From 3a5b56fcd3c2a580c002f76d26f30e33e01b32f7 Mon Sep 17 00:00:00 2001 From: pagedmov Date: Sat, 14 Mar 2026 01:04:03 -0400 Subject: [PATCH] more improvements to auto indent depth tracking added test cases for the auto indent/dedent feature --- src/readline/linebuf.rs | 116 ++++++++--- src/readline/mod.rs | 16 +- src/readline/tests.rs | 413 +++++++++++++++++++++------------------- 3 files changed, 310 insertions(+), 235 deletions(-) diff --git a/src/readline/linebuf.rs b/src/readline/linebuf.rs index 679469b..ca3b9af 100644 --- a/src/readline/linebuf.rs +++ b/src/readline/linebuf.rs @@ -15,7 +15,7 @@ use crate::{ libsh::{error::ShResult, guards::var_ctx_guard}, parse::{ execute::exec_input, - lex::{LexFlags, LexStream, QuoteState, Tk}, + lex::{LexFlags, LexStream, QuoteState, Tk, TkRule}, }, prelude::*, readline::{ @@ -351,14 +351,23 @@ impl ClampedUsize { } #[derive(Default, Clone, Debug)] -pub struct DepthCalc { +pub struct IndentCtx { depth: usize, ctx: Vec, + in_escaped_line: bool } -impl DepthCalc { +impl IndentCtx { pub fn new() -> Self { Self::default() } + pub fn depth(&self) -> usize { + self.depth + } + + pub fn ctx(&self) -> &[Tk] { + &self.ctx + } + pub fn descend(&mut self, tk: Tk) { self.ctx.push(tk); self.depth += 1; @@ -369,20 +378,28 @@ impl DepthCalc { self.ctx.pop(); } + pub fn reset(&mut self) { + std::mem::take(self); + } + pub fn check_tk(&mut self, tk: Tk) { if tk.is_opener() { self.descend(tk); } else if self.ctx.last().is_some_and(|t| tk.is_closer_for(t)) { self.ascend(); + } else if matches!(tk.class, TkRule::Sep) && self.in_escaped_line { + self.in_escaped_line = false; + self.depth = self.depth.saturating_sub(1); } } pub fn calculate(&mut self, input: &str) -> usize { - if input.ends_with("\\\n") { - self.depth += 1; // Line continuation, so we need to add an extra level - } - let input = Arc::new(input.to_string()); - let Ok(tokens) = LexStream::new(input.clone(), LexFlags::LEX_UNFINISHED).collect::>>() else { + self.depth = 0; + self.ctx.clear(); + self.in_escaped_line = false; + + let input_arc = Arc::new(input.to_string()); + let Ok(tokens) = LexStream::new(input_arc, LexFlags::LEX_UNFINISHED).collect::>>() else { log::error!("Lexing failed during depth calculation: {:?}", input); return 0; }; @@ -391,6 +408,11 @@ impl DepthCalc { self.check_tk(tk); } + if input.ends_with("\\\n") { + self.in_escaped_line = true; + self.depth += 1; + } + self.depth } } @@ -408,7 +430,7 @@ pub struct LineBuf { pub insert_mode_start_pos: Option, pub saved_col: Option, - pub auto_indent_level: usize, + pub indent_ctx: IndentCtx, pub undo_stack: Vec, pub redo_stack: Vec, @@ -662,6 +684,17 @@ impl LineBuf { pub fn read_slice_to_cursor(&self) -> Option<&str> { self.read_slice_to(self.cursor.get()) } + pub fn cursor_is_escaped(&mut self) -> bool { + let Some(to_cursor) = self.slice_to_cursor() else { + return false; + }; + + // count the number of backslashes + let delta = to_cursor.len() - to_cursor.trim_end_matches('\\').len(); + + // an even number of backslashes means each one is escaped + delta % 2 != 0 + } pub fn slice_to_cursor_inclusive(&mut self) -> Option<&str> { self.slice_to(self.cursor.ret_add(1)) } @@ -2076,15 +2109,13 @@ impl LineBuf { let end = start + (new.len().max(gr.len())); self.buffer.replace_range(start..end, new); } - pub fn calc_indent_level(&mut self) { + pub fn calc_indent_level(&mut self) -> usize { let to_cursor = self .slice_to_cursor() .map(|s| s.to_string()) .unwrap_or(self.buffer.clone()); - let mut calc = DepthCalc::new(); - - self.auto_indent_level = calc.calculate(&to_cursor); + self.indent_ctx.calculate(&to_cursor) } pub fn eval_motion(&mut self, verb: Option<&Verb>, motion: MotionCmd) -> MotionKind { let buffer = self.buffer.clone(); @@ -2661,8 +2692,8 @@ impl LineBuf { register.write_to_register(register_content); self.cursor.set(start); if do_indent { - self.calc_indent_level(); - let tabs = (0..self.auto_indent_level).map(|_| '\t'); + let depth = self.calc_indent_level(); + let tabs = (0..depth).map(|_| '\t'); for tab in tabs { self.insert_at_cursor(tab); self.cursor.add(1); @@ -2897,17 +2928,29 @@ impl LineBuf { }; end = end.saturating_sub(1); let mut last_was_whitespace = false; - for i in start..end { + let mut last_was_escape = false; + let mut i = start; + while i < end { let Some(gr) = self.grapheme_at(i) else { + i += 1; continue; }; if gr == "\n" { if last_was_whitespace { self.remove(i); + end -= 1; } else { self.force_replace_at(i, " "); } + if last_was_escape { + // if we are here, then we just joined an escaped newline + // semantically, echo foo\\nbar == echo foo bar + // so a joined line should remove the escape. + self.remove(i - 1); + end -= 1; + } last_was_whitespace = false; + last_was_escape = false; let strip_pos = if self.grapheme_at(i) == Some(" ") { i + 1 } else { @@ -2915,22 +2958,39 @@ impl LineBuf { }; while self.grapheme_at(strip_pos) == Some("\t") { self.remove(strip_pos); + end -= 1; } self.cursor.set(i); + i += 1; continue; - } - last_was_whitespace = is_whitespace(gr); + } else if gr == "\\" { + if last_was_whitespace && last_was_escape { + // if we are here, then the pattern of the last three chars was this: + // ' \\', a space and two backslashes. + // This means the "last" was an escaped backslash, not whitespace. + last_was_whitespace = false; + } + last_was_escape = !last_was_escape; + } else { + last_was_whitespace = is_whitespace(gr); + last_was_escape = false; + } + i += 1; } Ok(()) } fn verb_insert_char(&mut self, ch: char) { self.insert_at_cursor(ch); self.cursor.add(1); - let before = self.auto_indent_level; + let before_escaped = self.indent_ctx.in_escaped_line; + let before = self.indent_ctx.depth(); if read_shopts(|o| o.prompt.auto_indent) { - self.calc_indent_level(); - if self.auto_indent_level < before { - let delta = before - self.auto_indent_level; + let after = self.calc_indent_level(); + // Only dedent if the depth decrease came from a closer, not from + // a line continuation bonus going away + if after < before + && !(before_escaped && !self.indent_ctx.in_escaped_line) { + let delta = before - after; let line_start = self.start_of_line(); for _ in 0..delta { if self.grapheme_at(line_start).is_some_and(|gr| gr == "\t") { @@ -3021,8 +3081,8 @@ impl LineBuf { Anchor::After => { self.push('\n'); if auto_indent { - self.calc_indent_level(); - for _ in 0..self.auto_indent_level { + let depth = self.calc_indent_level(); + for _ in 0..depth { self.push('\t'); } } @@ -3031,8 +3091,8 @@ impl LineBuf { } Anchor::Before => { if auto_indent { - self.calc_indent_level(); - for _ in 0..self.auto_indent_level { + let depth = self.calc_indent_level(); + for _ in 0..depth { self.insert_at(0, '\t'); } } @@ -3059,8 +3119,8 @@ impl LineBuf { self.insert_at_cursor('\n'); self.cursor.add(1); if auto_indent { - self.calc_indent_level(); - for _ in 0..self.auto_indent_level { + let depth = self.calc_indent_level(); + for _ in 0..depth { self.insert_at_cursor('\t'); self.cursor.add(1); } diff --git a/src/readline/mod.rs b/src/readline/mod.rs index a206cb3..fe86cbc 100644 --- a/src/readline/mod.rs +++ b/src/readline/mod.rs @@ -253,7 +253,6 @@ pub struct ShedVi { pub repeat_action: Option, pub repeat_motion: Option, pub editor: LineBuf, - pub next_is_escaped: bool, pub old_layout: Option, pub history: History, @@ -271,7 +270,6 @@ impl ShedVi { completer: Box::new(FuzzyCompleter::default()), highlighter: Highlighter::new(), mode: Box::new(ViInsert::new()), - next_is_escaped: false, saved_mode: None, pending_keymap: Vec::new(), old_layout: None, @@ -303,7 +301,6 @@ impl ShedVi { completer: Box::new(FuzzyCompleter::default()), highlighter: Highlighter::new(), mode: Box::new(ViInsert::new()), - next_is_escaped: false, saved_mode: None, pending_keymap: Vec::new(), old_layout: None, @@ -417,7 +414,7 @@ impl ShedVi { LexStream::new(Arc::clone(&input), LexFlags::LEX_UNFINISHED).collect::>>(); let lex_result2 = LexStream::new(Arc::clone(&input), LexFlags::empty()).collect::>>(); - let is_top_level = self.editor.auto_indent_level == 0; + let is_top_level = self.editor.indent_ctx.ctx().is_empty(); let is_complete = match (lex_result1.is_err(), lex_result2.is_err()) { (true, true) => { @@ -808,14 +805,6 @@ impl ShedVi { } } - if let KeyEvent(KeyCode::Char('\\'), ModKeys::NONE) = key - && !self.next_is_escaped - { - self.next_is_escaped = true; - } else { - self.next_is_escaped = false; - } - let Ok(cmd) = self.mode.handle_key_fallible(key) else { // it's an ex mode error self.mode = Box::new(ViNormal::new()) as Box; @@ -834,8 +823,7 @@ impl ShedVi { } if cmd.is_submit_action() - && !self.next_is_escaped - && !self.editor.buffer.ends_with('\\') + && !self.editor.cursor_is_escaped() && (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete)) { if self.editor.attempt_history_expansion(&self.history) { diff --git a/src/readline/tests.rs b/src/readline/tests.rs index d774605..9ac6c9f 100644 --- a/src/readline/tests.rs +++ b/src/readline/tests.rs @@ -38,198 +38,225 @@ fn test_vi(initial: &str) -> (ShedVi, TestGuard) { // Why can't I marry a programming language vi_test! { - vi_dw_basic : "hello world" => "dw" => "world", 0; - vi_dw_middle : "one two three" => "wdw" => "one three", 4; - vi_dd_whole_line : "hello world" => "dd" => "", 0; - vi_x_single : "hello" => "x" => "ello", 0; - vi_x_middle : "hello" => "llx" => "helo", 2; - vi_X_backdelete : "hello" => "llX" => "hllo", 1; - vi_h_motion : "hello" => "$h" => "hello", 3; - vi_l_motion : "hello" => "l" => "hello", 1; - vi_h_at_start : "hello" => "h" => "hello", 0; - vi_l_at_end : "hello" => "$l" => "hello", 4; - vi_w_forward : "one two three" => "w" => "one two three", 4; - vi_b_backward : "one two three" => "$b" => "one two three", 8; - vi_e_end : "one two three" => "e" => "one two three", 2; - vi_ge_back_end : "one two three" => "$ge" => "one two three", 6; - vi_w_punctuation : "foo.bar baz" => "w" => "foo.bar baz", 3; - vi_e_punctuation : "foo.bar baz" => "e" => "foo.bar baz", 2; - vi_b_punctuation : "foo.bar baz" => "$b" => "foo.bar baz", 8; - vi_w_at_eol : "hello" => "$w" => "hello", 4; - vi_b_at_bol : "hello" => "b" => "hello", 0; - vi_W_forward : "foo.bar baz" => "W" => "foo.bar baz", 8; - vi_B_backward : "foo.bar baz" => "$B" => "foo.bar baz", 8; - vi_E_end : "foo.bar baz" => "E" => "foo.bar baz", 6; - vi_gE_back_end : "one two three" => "$gE" => "one two three", 6; - vi_W_skip_punct : "one-two three" => "W" => "one-two three", 8; - vi_B_skip_punct : "one two-three" => "$B" => "one two-three", 4; - vi_E_skip_punct : "one-two three" => "E" => "one-two three", 6; - vi_dW_big : "foo.bar baz" => "dW" => "baz", 0; - vi_cW_big : "foo.bar baz" => "cWx\x1b" => "x baz", 0; - vi_zero_bol : " hello" => "$0" => " hello", 0; - vi_caret_first_char : " hello" => "$^" => " hello", 2; - vi_dollar_eol : "hello world" => "$" => "hello world", 10; - vi_g_last_nonws : "hello " => "g_" => "hello ", 4; - vi_g_no_trailing : "hello" => "g_" => "hello", 4; - vi_pipe_column : "hello world" => "6|" => "hello world", 5; - vi_pipe_col1 : "hello world" => "1|" => "hello world", 0; - vi_I_insert_front : " hello" => "Iworld \x1b" => " world hello", 7; - vi_A_append_end : "hello" => "A world\x1b" => "hello world", 10; - vi_f_find : "hello world" => "fo" => "hello world", 4; - vi_F_find_back : "hello world" => "$Fo" => "hello world", 7; - vi_t_till : "hello world" => "tw" => "hello world", 5; - vi_T_till_back : "hello world" => "$To" => "hello world", 8; - vi_f_no_match : "hello" => "fz" => "hello", 0; - vi_semicolon_repeat : "abcabc" => "fa;;" => "abcabc", 3; - vi_comma_reverse : "abcabc" => "fa;;," => "abcabc", 0; - vi_df_semicolon : "abcabc" => "fa;;dfa" => "abcabc", 3; - vi_t_at_target : "aab" => "lta" => "aab", 1; - vi_D_to_end : "hello world" => "wD" => "hello ", 5; - vi_d_dollar : "hello world" => "wd$" => "hello ", 5; - vi_d0_to_start : "hello world" => "$d0" => "d", 0; - vi_dw_multiple : "one two three" => "d2w" => "three", 0; - vi_dt_char : "hello world" => "dtw" => "world", 0; - vi_df_char : "hello world" => "dfw" => "orld", 0; - vi_dh_back : "hello" => "lldh" => "hllo", 1; - vi_dl_forward : "hello" => "dl" => "ello", 0; - vi_dge_back_end : "one two three" => "$dge" => "one tw", 5; - vi_dG_to_end : "hello world" => "dG" => "", 0; - vi_dgg_to_start : "hello world" => "$dgg" => "", 0; - vi_d_semicolon : "abcabc" => "fad;" => "abcabc", 3; - vi_cw_basic : "hello world" => "cwfoo\x1b" => "foo world", 2; - vi_C_to_end : "hello world" => "wCfoo\x1b" => "hello foo", 8; - vi_cc_whole : "hello world" => "ccfoo\x1b" => "foo", 2; - vi_ct_char : "hello world" => "ctwfoo\x1b" => "fooworld", 2; - vi_s_single : "hello" => "sfoo\x1b" => "fooello", 2; - vi_S_whole_line : "hello world" => "Sfoo\x1b" => "foo", 2; - vi_cl_forward : "hello" => "clX\x1b" => "Xello", 0; - vi_ch_backward : "hello" => "llchX\x1b" => "hXllo", 1; - vi_cb_word_back : "hello world" => "$cbfoo\x1b" => "hello food", 8; - vi_ce_word_end : "hello world" => "cefoo\x1b" => "foo world", 2; - vi_c0_to_start : "hello world" => "wc0foo\x1b" => "fooworld", 2; - vi_yw_p_basic : "hello world" => "ywwP" => "hello hello world", 11; - vi_dw_p_paste : "hello world" => "dwP" => "hello world", 5; - vi_dd_p_paste : "hello world" => "ddp" => "\nhello world", 1; - vi_y_dollar_p : "hello world" => "wy$P" => "hello worldworld", 10; - vi_ye_p : "hello world" => "yewP" => "hello helloworld", 10; - vi_yy_p : "hello world" => "yyp" => "hello world\nhello world", 12; - vi_Y_p : "hello world" => "Yp" => "hhello worldello world", 11; - vi_p_after_x : "hello" => "xp" => "ehllo", 1; - vi_P_before : "hello" => "llxP" => "hello", 2; - vi_paste_empty : "hello" => "p" => "hello", 0; - vi_r_replace : "hello" => "ra" => "aello", 0; - vi_r_middle : "hello" => "llra" => "healo", 2; - vi_r_at_end : "hello" => "$ra" => "hella", 4; - vi_r_space : "hello" => "r " => " ello", 0; - vi_r_with_count : "hello" => "3rx" => "xxxlo", 2; - vi_tilde_single : "hello" => "~" => "Hello", 1; - vi_tilde_count : "hello" => "3~" => "HELlo", 3; - vi_tilde_at_end : "HELLO" => "$~" => "HELLo", 4; - vi_tilde_mixed : "hElLo" => "5~" => "HeLlO", 4; - vi_gu_word : "HELLO world" => "guw" => "hello world", 0; - vi_gU_word : "hello WORLD" => "gUw" => "HELLO WORLD", 0; - vi_gu_dollar : "HELLO WORLD" => "gu$" => "hello world", 0; - vi_gU_dollar : "hello world" => "gU$" => "HELLO WORLD", 0; - vi_gu_0 : "HELLO WORLD" => "$gu0" => "hello worlD", 0; - vi_gU_0 : "hello world" => "$gU0" => "HELLO WORLd", 0; - vi_gtilde_word : "hello WORLD" => "g~w" => "HELLO WORLD", 0; - vi_gtilde_dollar : "hello WORLD" => "g~$" => "HELLO world", 0; - vi_diw_inner : "one two three" => "wdiw" => "one three", 4; - vi_ciw_replace : "hello world" => "ciwfoo\x1b" => "foo world", 2; - vi_daw_around : "one two three" => "wdaw" => "one three", 4; - vi_yiw_p : "hello world" => "yiwAp \x1bp" => "hello worldp hello", 17; - vi_diW_big_inner : "one-two three" => "diW" => " three", 0; - vi_daW_big_around : "one two-three end" => "wdaW" => "one end", 4; - vi_ciW_big : "one-two three" => "ciWx\x1b" => "x three", 0; - vi_di_dquote : "one \"two\" three" => "f\"di\"" => "one \"\" three", 5; - vi_da_dquote : "one \"two\" three" => "f\"da\"" => "one three", 4; - vi_ci_dquote : "one \"two\" three" => "f\"ci\"x\x1b" => "one \"x\" three", 5; - vi_di_squote : "one 'two' three" => "f'di'" => "one '' three", 5; - vi_da_squote : "one 'two' three" => "f'da'" => "one three", 4; - vi_di_backtick : "one `two` three" => "f`di`" => "one `` three", 5; - vi_da_backtick : "one `two` three" => "f`da`" => "one three", 4; - vi_ci_dquote_empty : "one \"\" three" => "f\"ci\"x\x1b" => "one \"x\" three", 5; - vi_di_paren : "one (two) three" => "f(di(" => "one () three", 5; - vi_da_paren : "one (two) three" => "f(da(" => "one three", 4; - vi_ci_paren : "one (two) three" => "f(ci(x\x1b" => "one (x) three", 5; - vi_di_brace : "one {two} three" => "f{di{" => "one {} three", 5; - vi_da_brace : "one {two} three" => "f{da{" => "one three", 4; - vi_di_bracket : "one [two] three" => "f[di[" => "one [] three", 5; - vi_da_bracket : "one [two] three" => "f[da[" => "one three", 4; - vi_di_angle : "one three" => "f "one <> three", 5; - vi_da_angle : "one three" => "f "one three", 4; - vi_di_paren_nested : "fn(a, (b, c))" => "f(di(" => "fn()", 3; - vi_di_paren_empty : "fn() end" => "f(di(" => "fn() end", 3; - vi_dib_alias : "one (two) three" => "f(dib" => "one () three", 5; - vi_diB_alias : "one {two} three" => "f{diB" => "one {} three", 5; - vi_percent_paren : "(hello) world" => "%" => "(hello) world", 6; - vi_percent_brace : "{hello} world" => "%" => "{hello} world", 6; - vi_percent_bracket : "[hello] world" => "%" => "[hello] world", 6; - vi_percent_from_close: "(hello) world" => "f)%" => "(hello) world", 0; - vi_d_percent_paren : "(hello) world" => "d%" => " world", 0; - vi_i_insert : "hello" => "iX\x1b" => "Xhello", 0; - vi_a_append : "hello" => "aX\x1b" => "hXello", 1; - vi_I_front : " hello" => "IX\x1b" => " Xhello", 2; - vi_A_end : "hello" => "AX\x1b" => "helloX", 5; - vi_o_open_below : "hello" => "oworld\x1b" => "hello\nworld", 10; - vi_O_open_above : "hello" => "Oworld\x1b" => "world\nhello", 4; - vi_empty_input : "" => "i hello\x1b" => " hello", 5; - vi_insert_escape : "hello" => "aX\x1b" => "hXello", 1; - vi_ctrl_w_del_word : "hello world" => "A\x17\x1b" => "hello ", 5; - vi_ctrl_h_backspace : "hello" => "A\x08\x1b" => "hell", 3; - vi_u_undo_delete : "hello world" => "dwu" => "hello world", 0; - vi_u_undo_change : "hello world" => "ciwfoo\x1bu" => "hello world", 0; - vi_u_undo_x : "hello" => "xu" => "hello", 0; - vi_ctrl_r_redo : "hello" => "xu\x12" => "ello", 0; - vi_u_multiple : "hello world" => "xdwu" => "ello world", 0; - vi_redo_after_undo : "hello world" => "dwu\x12" => "world", 0; - vi_dot_repeat_x : "hello" => "x." => "llo", 0; - vi_dot_repeat_dw : "one two three" => "dw." => "three", 0; - vi_dot_repeat_cw : "one two three" => "cwfoo\x1bw." => "foo foo three", 6; - vi_dot_repeat_r : "hello" => "ra.." => "aello", 0; - vi_dot_repeat_s : "hello" => "sX\x1bl." => "XXllo", 1; - vi_count_h : "hello world" => "$3h" => "hello world", 7; - vi_count_l : "hello world" => "3l" => "hello world", 3; - vi_count_w : "one two three four" => "2w" => "one two three four", 8; - vi_count_b : "one two three four" => "$2b" => "one two three four", 8; - vi_count_x : "hello" => "3x" => "lo", 0; - vi_count_dw : "one two three four" => "2dw" => "three four", 0; - vi_verb_count_motion : "one two three four" => "d2w" => "three four", 0; - vi_count_s : "hello" => "3sX\x1b" => "Xlo", 0; - vi_indent_line : "hello" => ">>" => "\thello", 1; - vi_dedent_line : "\thello" => "<<" => "hello", 0; - vi_indent_double : "hello" => ">>>>" => "\t\thello", 2; - vi_J_join_lines : "hello\nworld" => "J" => "hello world", 5; - vi_v_u_lower : "HELLO" => "vlllu" => "hellO", 0; - vi_v_U_upper : "hello" => "vlllU" => "HELLo", 0; - vi_v_d_delete : "hello world" => "vwwd" => "", 0; - vi_v_x_delete : "hello world" => "vwwx" => "", 0; - vi_v_c_change : "hello world" => "vwcfoo\x1b" => "fooorld", 2; - vi_v_y_p_yank : "hello world" => "vwyAp \x1bp" => "hello worldp hello w", 19; - vi_v_dollar_d : "hello world" => "wv$d" => "hello ", 5; - vi_v_0_d : "hello world" => "$v0d" => "", 0; - vi_ve_d : "hello world" => "ved" => " world", 0; - vi_v_o_swap : "hello world" => "vllod" => "lo world", 0; - vi_v_r_replace : "hello" => "vlllrx" => "xxxxo", 0; - vi_v_tilde_case : "hello" => "vlll~" => "HELLo", 0; - vi_V_d_delete : "hello world" => "Vd" => "", 0; - vi_V_y_p : "hello world" => "Vyp" => "hello world\nhello world", 12; - vi_V_S_change : "hello world" => "VSfoo\x1b" => "foo", 2; - vi_ctrl_a_inc : "num 5 end" => "w\x01" => "num 6 end", 4; - vi_ctrl_x_dec : "num 5 end" => "w\x18" => "num 4 end", 4; - vi_ctrl_a_negative : "num -3 end" => "w\x01" => "num -2 end", 4; - vi_ctrl_x_to_neg : "num 0 end" => "w\x18" => "num -1 end", 4; - vi_ctrl_a_count : "num 5 end" => "w3\x01" => "num 8 end", 4; - vi_ctrl_a_width : "num -00001 end" => "w\x01" => "num 00000 end", 4; - vi_delete_empty : "" => "x" => "", 0; - vi_undo_on_empty : "" => "u" => "", 0; - vi_w_single_char : "a b c" => "w" => "a b c", 2; - vi_dw_last_word : "hello" => "dw" => "", 0; - vi_dollar_single : "h" => "$" => "h", 0; - vi_caret_no_ws : "hello" => "$^" => "hello", 0; - vi_f_last_char : "hello" => "fo" => "hello", 4; - vi_r_on_space : "hello world" => "5|r-" => "hell- world", 4; - vi_vw_doesnt_crash : "" => "vw" => "", 0; - vi_indent_cursor_pos : "echo foo" => ">>" => "\techo foo", 1; + vi_dw_basic : "hello world" => "dw" => "world", 0; + vi_dw_middle : "one two three" => "wdw" => "one three", 4; + vi_dd_whole_line : "hello world" => "dd" => "", 0; + vi_x_single : "hello" => "x" => "ello", 0; + vi_x_middle : "hello" => "llx" => "helo", 2; + vi_X_backdelete : "hello" => "llX" => "hllo", 1; + vi_h_motion : "hello" => "$h" => "hello", 3; + vi_l_motion : "hello" => "l" => "hello", 1; + vi_h_at_start : "hello" => "h" => "hello", 0; + vi_l_at_end : "hello" => "$l" => "hello", 4; + vi_w_forward : "one two three" => "w" => "one two three", 4; + vi_b_backward : "one two three" => "$b" => "one two three", 8; + vi_e_end : "one two three" => "e" => "one two three", 2; + vi_ge_back_end : "one two three" => "$ge" => "one two three", 6; + vi_w_punctuation : "foo.bar baz" => "w" => "foo.bar baz", 3; + vi_e_punctuation : "foo.bar baz" => "e" => "foo.bar baz", 2; + vi_b_punctuation : "foo.bar baz" => "$b" => "foo.bar baz", 8; + vi_w_at_eol : "hello" => "$w" => "hello", 4; + vi_b_at_bol : "hello" => "b" => "hello", 0; + vi_W_forward : "foo.bar baz" => "W" => "foo.bar baz", 8; + vi_B_backward : "foo.bar baz" => "$B" => "foo.bar baz", 8; + vi_E_end : "foo.bar baz" => "E" => "foo.bar baz", 6; + vi_gE_back_end : "one two three" => "$gE" => "one two three", 6; + vi_W_skip_punct : "one-two three" => "W" => "one-two three", 8; + vi_B_skip_punct : "one two-three" => "$B" => "one two-three", 4; + vi_E_skip_punct : "one-two three" => "E" => "one-two three", 6; + vi_dW_big : "foo.bar baz" => "dW" => "baz", 0; + vi_cW_big : "foo.bar baz" => "cWx\x1b" => "x baz", 0; + vi_zero_bol : " hello" => "$0" => " hello", 0; + vi_caret_first_char : " hello" => "$^" => " hello", 2; + vi_dollar_eol : "hello world" => "$" => "hello world", 10; + vi_g_last_nonws : "hello " => "g_" => "hello ", 4; + vi_g_no_trailing : "hello" => "g_" => "hello", 4; + vi_pipe_column : "hello world" => "6|" => "hello world", 5; + vi_pipe_col1 : "hello world" => "1|" => "hello world", 0; + vi_I_insert_front : " hello" => "Iworld \x1b" => " world hello", 7; + vi_A_append_end : "hello" => "A world\x1b" => "hello world", 10; + vi_f_find : "hello world" => "fo" => "hello world", 4; + vi_F_find_back : "hello world" => "$Fo" => "hello world", 7; + vi_t_till : "hello world" => "tw" => "hello world", 5; + vi_T_till_back : "hello world" => "$To" => "hello world", 8; + vi_f_no_match : "hello" => "fz" => "hello", 0; + vi_semicolon_repeat : "abcabc" => "fa;;" => "abcabc", 3; + vi_comma_reverse : "abcabc" => "fa;;," => "abcabc", 0; + vi_df_semicolon : "abcabc" => "fa;;dfa" => "abcabc", 3; + vi_t_at_target : "aab" => "lta" => "aab", 1; + vi_D_to_end : "hello world" => "wD" => "hello ", 5; + vi_d_dollar : "hello world" => "wd$" => "hello ", 5; + vi_d0_to_start : "hello world" => "$d0" => "d", 0; + vi_dw_multiple : "one two three" => "d2w" => "three", 0; + vi_dt_char : "hello world" => "dtw" => "world", 0; + vi_df_char : "hello world" => "dfw" => "orld", 0; + vi_dh_back : "hello" => "lldh" => "hllo", 1; + vi_dl_forward : "hello" => "dl" => "ello", 0; + vi_dge_back_end : "one two three" => "$dge" => "one tw", 5; + vi_dG_to_end : "hello world" => "dG" => "", 0; + vi_dgg_to_start : "hello world" => "$dgg" => "", 0; + vi_d_semicolon : "abcabc" => "fad;" => "abcabc", 3; + vi_cw_basic : "hello world" => "cwfoo\x1b" => "foo world", 2; + vi_C_to_end : "hello world" => "wCfoo\x1b" => "hello foo", 8; + vi_cc_whole : "hello world" => "ccfoo\x1b" => "foo", 2; + vi_ct_char : "hello world" => "ctwfoo\x1b" => "fooworld", 2; + vi_s_single : "hello" => "sfoo\x1b" => "fooello", 2; + vi_S_whole_line : "hello world" => "Sfoo\x1b" => "foo", 2; + vi_cl_forward : "hello" => "clX\x1b" => "Xello", 0; + vi_ch_backward : "hello" => "llchX\x1b" => "hXllo", 1; + vi_cb_word_back : "hello world" => "$cbfoo\x1b" => "hello food", 8; + vi_ce_word_end : "hello world" => "cefoo\x1b" => "foo world", 2; + vi_c0_to_start : "hello world" => "wc0foo\x1b" => "fooworld", 2; + vi_yw_p_basic : "hello world" => "ywwP" => "hello hello world", 11; + vi_dw_p_paste : "hello world" => "dwP" => "hello world", 5; + vi_dd_p_paste : "hello world" => "ddp" => "\nhello world", 1; + vi_y_dollar_p : "hello world" => "wy$P" => "hello worldworld", 10; + vi_ye_p : "hello world" => "yewP" => "hello helloworld", 10; + vi_yy_p : "hello world" => "yyp" => "hello world\nhello world", 12; + vi_Y_p : "hello world" => "Yp" => "hhello worldello world", 11; + vi_p_after_x : "hello" => "xp" => "ehllo", 1; + vi_P_before : "hello" => "llxP" => "hello", 2; + vi_paste_empty : "hello" => "p" => "hello", 0; + vi_r_replace : "hello" => "ra" => "aello", 0; + vi_r_middle : "hello" => "llra" => "healo", 2; + vi_r_at_end : "hello" => "$ra" => "hella", 4; + vi_r_space : "hello" => "r " => " ello", 0; + vi_r_with_count : "hello" => "3rx" => "xxxlo", 2; + vi_tilde_single : "hello" => "~" => "Hello", 1; + vi_tilde_count : "hello" => "3~" => "HELlo", 3; + vi_tilde_at_end : "HELLO" => "$~" => "HELLo", 4; + vi_tilde_mixed : "hElLo" => "5~" => "HeLlO", 4; + vi_gu_word : "HELLO world" => "guw" => "hello world", 0; + vi_gU_word : "hello WORLD" => "gUw" => "HELLO WORLD", 0; + vi_gu_dollar : "HELLO WORLD" => "gu$" => "hello world", 0; + vi_gU_dollar : "hello world" => "gU$" => "HELLO WORLD", 0; + vi_gu_0 : "HELLO WORLD" => "$gu0" => "hello worlD", 0; + vi_gU_0 : "hello world" => "$gU0" => "HELLO WORLd", 0; + vi_gtilde_word : "hello WORLD" => "g~w" => "HELLO WORLD", 0; + vi_gtilde_dollar : "hello WORLD" => "g~$" => "HELLO world", 0; + vi_diw_inner : "one two three" => "wdiw" => "one three", 4; + vi_ciw_replace : "hello world" => "ciwfoo\x1b" => "foo world", 2; + vi_daw_around : "one two three" => "wdaw" => "one three", 4; + vi_yiw_p : "hello world" => "yiwAp \x1bp" => "hello worldp hello", 17; + vi_diW_big_inner : "one-two three" => "diW" => " three", 0; + vi_daW_big_around : "one two-three end" => "wdaW" => "one end", 4; + vi_ciW_big : "one-two three" => "ciWx\x1b" => "x three", 0; + vi_di_dquote : "one \"two\" three" => "f\"di\"" => "one \"\" three", 5; + vi_da_dquote : "one \"two\" three" => "f\"da\"" => "one three", 4; + vi_ci_dquote : "one \"two\" three" => "f\"ci\"x\x1b" => "one \"x\" three", 5; + vi_di_squote : "one 'two' three" => "f'di'" => "one '' three", 5; + vi_da_squote : "one 'two' three" => "f'da'" => "one three", 4; + vi_di_backtick : "one `two` three" => "f`di`" => "one `` three", 5; + vi_da_backtick : "one `two` three" => "f`da`" => "one three", 4; + vi_ci_dquote_empty : "one \"\" three" => "f\"ci\"x\x1b" => "one \"x\" three", 5; + vi_di_paren : "one (two) three" => "f(di(" => "one () three", 5; + vi_da_paren : "one (two) three" => "f(da(" => "one three", 4; + vi_ci_paren : "one (two) three" => "f(ci(x\x1b" => "one (x) three", 5; + vi_di_brace : "one {two} three" => "f{di{" => "one {} three", 5; + vi_da_brace : "one {two} three" => "f{da{" => "one three", 4; + vi_di_bracket : "one [two] three" => "f[di[" => "one [] three", 5; + vi_da_bracket : "one [two] three" => "f[da[" => "one three", 4; + vi_di_angle : "one three" => "f "one <> three", 5; + vi_da_angle : "one three" => "f "one three", 4; + vi_di_paren_nested : "fn(a, (b, c))" => "f(di(" => "fn()", 3; + vi_di_paren_empty : "fn() end" => "f(di(" => "fn() end", 3; + vi_dib_alias : "one (two) three" => "f(dib" => "one () three", 5; + vi_diB_alias : "one {two} three" => "f{diB" => "one {} three", 5; + vi_percent_paren : "(hello) world" => "%" => "(hello) world", 6; + vi_percent_brace : "{hello} world" => "%" => "{hello} world", 6; + vi_percent_bracket : "[hello] world" => "%" => "[hello] world", 6; + vi_percent_from_close: "(hello) world" => "f)%" => "(hello) world", 0; + vi_d_percent_paren : "(hello) world" => "d%" => " world", 0; + vi_i_insert : "hello" => "iX\x1b" => "Xhello", 0; + vi_a_append : "hello" => "aX\x1b" => "hXello", 1; + vi_I_front : " hello" => "IX\x1b" => " Xhello", 2; + vi_A_end : "hello" => "AX\x1b" => "helloX", 5; + vi_o_open_below : "hello" => "oworld\x1b" => "hello\nworld", 10; + vi_O_open_above : "hello" => "Oworld\x1b" => "world\nhello", 4; + vi_empty_input : "" => "i hello\x1b" => " hello", 5; + vi_insert_escape : "hello" => "aX\x1b" => "hXello", 1; + vi_ctrl_w_del_word : "hello world" => "A\x17\x1b" => "hello ", 5; + vi_ctrl_h_backspace : "hello" => "A\x08\x1b" => "hell", 3; + vi_u_undo_delete : "hello world" => "dwu" => "hello world", 0; + vi_u_undo_change : "hello world" => "ciwfoo\x1bu" => "hello world", 0; + vi_u_undo_x : "hello" => "xu" => "hello", 0; + vi_ctrl_r_redo : "hello" => "xu\x12" => "ello", 0; + vi_u_multiple : "hello world" => "xdwu" => "ello world", 0; + vi_redo_after_undo : "hello world" => "dwu\x12" => "world", 0; + vi_dot_repeat_x : "hello" => "x." => "llo", 0; + vi_dot_repeat_dw : "one two three" => "dw." => "three", 0; + vi_dot_repeat_cw : "one two three" => "cwfoo\x1bw." => "foo foo three", 6; + vi_dot_repeat_r : "hello" => "ra.." => "aello", 0; + vi_dot_repeat_s : "hello" => "sX\x1bl." => "XXllo", 1; + vi_count_h : "hello world" => "$3h" => "hello world", 7; + vi_count_l : "hello world" => "3l" => "hello world", 3; + vi_count_w : "one two three four" => "2w" => "one two three four", 8; + vi_count_b : "one two three four" => "$2b" => "one two three four", 8; + vi_count_x : "hello" => "3x" => "lo", 0; + vi_count_dw : "one two three four" => "2dw" => "three four", 0; + vi_verb_count_motion : "one two three four" => "d2w" => "three four", 0; + vi_count_s : "hello" => "3sX\x1b" => "Xlo", 0; + vi_indent_line : "hello" => ">>" => "\thello", 1; + vi_dedent_line : "\thello" => "<<" => "hello", 0; + vi_indent_double : "hello" => ">>>>" => "\t\thello", 2; + vi_J_join_lines : "hello\nworld" => "J" => "hello world", 5; + vi_v_u_lower : "HELLO" => "vlllu" => "hellO", 0; + vi_v_U_upper : "hello" => "vlllU" => "HELLo", 0; + vi_v_d_delete : "hello world" => "vwwd" => "", 0; + vi_v_x_delete : "hello world" => "vwwx" => "", 0; + vi_v_c_change : "hello world" => "vwcfoo\x1b" => "fooorld", 2; + vi_v_y_p_yank : "hello world" => "vwyAp \x1bp" => "hello worldp hello w", 19; + vi_v_dollar_d : "hello world" => "wv$d" => "hello ", 5; + vi_v_0_d : "hello world" => "$v0d" => "", 0; + vi_ve_d : "hello world" => "ved" => " world", 0; + vi_v_o_swap : "hello world" => "vllod" => "lo world", 0; + vi_v_r_replace : "hello" => "vlllrx" => "xxxxo", 0; + vi_v_tilde_case : "hello" => "vlll~" => "HELLo", 0; + vi_V_d_delete : "hello world" => "Vd" => "", 0; + vi_V_y_p : "hello world" => "Vyp" => "hello world\nhello world", 12; + vi_V_S_change : "hello world" => "VSfoo\x1b" => "foo", 2; + vi_ctrl_a_inc : "num 5 end" => "w\x01" => "num 6 end", 4; + vi_ctrl_x_dec : "num 5 end" => "w\x18" => "num 4 end", 4; + vi_ctrl_a_negative : "num -3 end" => "w\x01" => "num -2 end", 4; + vi_ctrl_x_to_neg : "num 0 end" => "w\x18" => "num -1 end", 4; + vi_ctrl_a_count : "num 5 end" => "w3\x01" => "num 8 end", 4; + vi_ctrl_a_width : "num -00001 end" => "w\x01" => "num 00000 end", 4; + vi_delete_empty : "" => "x" => "", 0; + vi_undo_on_empty : "" => "u" => "", 0; + vi_w_single_char : "a b c" => "w" => "a b c", 2; + vi_dw_last_word : "hello" => "dw" => "", 0; + vi_dollar_single : "h" => "$" => "h", 0; + vi_caret_no_ws : "hello" => "$^" => "hello", 0; + vi_f_last_char : "hello" => "fo" => "hello", 4; + vi_r_on_space : "hello world" => "5|r-" => "hell- world", 4; + vi_vw_doesnt_crash : "" => "vw" => "", 0; + vi_indent_cursor_pos : "echo foo" => ">>" => "\techo foo", 1; vi_join_indent_lines : "echo foo\n\t\techo bar" => "J" => "echo foo echo bar", 8 } + +#[test] +fn vi_auto_indent() { + let (mut vi, _g) = test_vi(""); + + // Type each line and press Enter separately so auto-indent triggers + let lines = [ + "func() {", + "case foo in", + "bar)", + "while true; do", + "echo foo \\\rbar \\\rbiz \\\rbazz\rbreak\rdone\r;;\resac\r}" + ]; + + for (i,line) in lines.iter().enumerate() { + vi.feed_bytes(line.as_bytes()); + if i != lines.len() - 1 { + vi.feed_bytes(b"\r"); + } + vi.process_input().unwrap(); + } + + assert_eq!( + vi.editor.as_str(), + "func() {\n\tcase foo in\n\t\tbar)\n\t\t\twhile true; do\n\t\t\t\techo foo \\\n\t\t\t\t\tbar \\\n\t\t\t\t\tbiz \\\n\t\t\t\t\tbazz\n\t\t\t\tbreak\n\t\t\tdone\n\t\t;;\n\tesac\n}" + ); +}