Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/tests/test/auto_pairs.rs')
| -rw-r--r-- | helix-term/tests/test/auto_pairs.rs | 872 |
1 files changed, 869 insertions, 3 deletions
diff --git a/helix-term/tests/test/auto_pairs.rs b/helix-term/tests/test/auto_pairs.rs index c921e2ae..8048d97f 100644 --- a/helix-term/tests/test/auto_pairs.rs +++ b/helix-term/tests/test/auto_pairs.rs @@ -16,10 +16,119 @@ fn matching_pairs() -> impl Iterator<Item = &'static (char, char)> { async fn insert_basic() -> anyhow::Result<()> { for pair in DEFAULT_PAIRS { test(( - format!("#[{}|]#", LINE_END), + "#[\n|]#", format!("i{}", pair.0), - format!("{}#[|{}]#{}", pair.0, pair.1, LINE_END), - LineFeedHandling::AsIs, + format!("{}#[|{}]#", pair.0, pair.1), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn insert_whitespace() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("{}#[|{}]#", pair.0, pair.1), + "i ", + format!("{} #[| ]#{}", pair.0, pair.1), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn insert_whitespace_multi() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!( + indoc! {"\ + {open}#[|{close}]# + {open}#(|{open})#{close}{close} + {open}{open}#(|{close}{close})# + foo#(|\n)# + "}, + open = pair.0, + close = pair.1, + ), + "i ", + format!( + indoc! {"\ + {open} #[| ]#{close} + {open} #(|{open})#{close}{close} + {open}{open} #(| {close}{close})# + foo #(|\n)# + "}, + open = pair.0, + close = pair.1, + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn append_whitespace_multi() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!( + indoc! {"\ + #[|{open}]#{close} + #(|{open})#{open}{close}{close} + #(|{open}{open})#{close}{close} + #(|foo)# + "}, + open = pair.0, + close = pair.1, + ), + "a ", + format!( + indoc! {"\ + #[{open} |]#{close} + #({open} {open}|)#{close}{close} + #({open}{open} |)#{close}{close} + #(foo \n|)# + "}, + open = pair.0, + close = pair.1, + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn insert_whitespace_no_pair() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + // sanity check - do not insert extra whitespace unless immediately + // surrounded by a pair + test(( + format!("{} #[|{}]#", pair.0, pair.1), + "i ", + format!("{} #[|{}]#", pair.0, pair.1), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn insert_whitespace_no_matching_pair() -> anyhow::Result<()> { + for pair in differing_pairs() { + // sanity check - verify whitespace does not insert unless both pairs + // are matches, i.e. no two different openers + test(( + format!("{}#[|{}]#", pair.0, pair.0), + "i ", + format!("{} #[|{}]#", pair.0, pair.0), )) .await?; } @@ -567,3 +676,760 @@ async fn append_inside_nested_pair_multi() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn delete_basic() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("{}#[|{}]#{}", pair.0, pair.1, LINE_END), + "i<backspace>", + format!("#[|{}]#", LINE_END), + LineFeedHandling::AsIs, + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_multi() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + indoc! {"\ + {open}#[|{close}]# + {open}#(|{close})# + {open}#(|{close})# + "}, + open = pair.0, + close = pair.1, + ), + "i<backspace>", + indoc! {"\ + #[|\n]# + #(|\n)# + #(|\n)# + "}, + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_whitespace() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("{} #[| ]#{}", pair.0, pair.1), + "i<backspace>", + format!("{}#[|{}]#", pair.0, pair.1), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_whitespace_after_word() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("foo{} #[| ]#{}", pair.0, pair.1), + "i<backspace>", + format!("foo{}#[|{}]#", pair.0, pair.1), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_whitespace_multi() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + indoc! {"\ + {open} #[| ]#{close} + {open} #(|{open})#{close}{close} + {open}{open} #(| {close}{close})# + foo #(|\n)# + "}, + open = pair.0, + close = pair.1, + ), + "i<backspace>", + format!( + indoc! {"\ + {open}#[|{close}]# + {open}#(|{open})#{close}{close} + {open}{open}#(|{close}{close})# + foo#(|\n)# + "}, + open = pair.0, + close = pair.1, + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_append_whitespace_multi() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + indoc! {"\ + #[{open} |]# {close} + #({open} |)#{open}{close}{close} + #({open}{open} |)# {close}{close} + #(foo |)# + "}, + open = pair.0, + close = pair.1, + ), + "a<backspace>", + format!( + indoc! {"\ + #[{open}{close}|]# + #({open}{open}|)#{close}{close} + #({open}{open}{close}|)#{close} + #(foo\n|)# + "}, + open = pair.0, + close = pair.1, + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_whitespace_no_pair() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("{} #[|{}]#", pair.0, pair.1), + "i<backspace>", + format!("{} #[|{}]#", pair.0, pair.1), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_whitespace_no_matching_pair() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!("{} #[|{}]#", pair.0, pair.0), + "i<backspace>", + format!("{}#[|{}]#", pair.0, pair.0), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_configured_multi_byte_chars() -> anyhow::Result<()> { + // NOTE: these are multi-byte Unicode characters + let pairs = hashmap!('„' => '“', '‚' => '‘', '「' => '」'); + + let config = Config { + editor: helix_view::editor::Config { + auto_pairs: AutoPairConfig::Pairs(pairs.clone()), + ..Default::default() + }, + ..Default::default() + }; + + for (open, close) in pairs.iter() { + test_with_config( + AppBuilder::new().with_config(config.clone()), + ( + format!("{}#[|{}]#{}", open, close, LINE_END), + "i<backspace>", + format!("#[|{}]#", LINE_END), + LineFeedHandling::AsIs, + ), + ) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_after_word() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + &format!("foo{}#[|{}]#", pair.0, pair.1), + "i<backspace>", + "foo#[|\n]#", + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn insert_then_delete() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + "#[\n|]#\n", + format!("ofoo{}<backspace>", pair.0), + "\nfoo#[\n|]#\n", + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn insert_then_delete_whitespace() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + "foo#[\n|]#", + format!("i{}<space><backspace><backspace>", pair.0), + "foo#[|\n]#", + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn insert_then_delete_multi() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + indoc! {"\ + through a day#[\n|]# + in and out of weeks#(\n|)# + over a year#(\n|)# + "}, + format!("i{}<space><backspace><backspace>", pair.0), + indoc! {"\ + through a day#[|\n]# + in and out of weeks#(|\n)# + over a year#(|\n)# + "}, + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn append_then_delete() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + "fo#[o|]#", + format!("a{}<space><backspace><backspace>", pair.0), + "fo#[o\n|]#", + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn append_then_delete_multi() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + indoc! {"\ + #[through a day|]# + #(in and out of weeks|)# + #(over a year|)# + "}, + format!("a{}<space><backspace><backspace>", pair.0), + indoc! {"\ + #[through a day\n|]# + #(in and out of weeks\n|)# + #(over a year\n|)# + "}, + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_before_word() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + // sanity check unclosed pair delete + test(( + format!("{}#[|f]#oo{}", pair.0, LINE_END), + "i<backspace>", + format!("#[|f]#oo{}", LINE_END), + )) + .await?; + + // deleting the closing pair should NOT delete the whole pair + test(( + format!("{}{}#[|f]#oo{}", pair.0, pair.1, LINE_END), + "i<backspace>", + format!("{}#[|f]#oo{}", pair.0, LINE_END), + )) + .await?; + + // deleting whole pair before word + test(( + format!("{}#[|{}]#foo{}", pair.0, pair.1, LINE_END), + "i<backspace>", + format!("#[|f]#oo{}", LINE_END), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_before_word_selection() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + // sanity check unclosed pair delete + test(( + format!("{}#[|foo]#{}", pair.0, LINE_END), + "i<backspace>", + format!("#[|foo]#{}", LINE_END), + )) + .await?; + + // deleting the closing pair should NOT delete the whole pair + test(( + format!("{}{}#[|foo]#{}", pair.0, pair.1, LINE_END), + "i<backspace>", + format!("{}#[|foo]#{}", pair.0, LINE_END), + )) + .await?; + + // deleting whole pair before word + test(( + format!("{}#[|{}foo]#{}", pair.0, pair.1, LINE_END), + "i<backspace>", + format!("#[|foo]#{}", LINE_END), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_before_word_selection_trailing_word() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("foo{}#[|{} wor]#{}", pair.0, pair.1, LINE_END), + "i<backspace>", + format!("foo#[| wor]#{}", LINE_END), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_before_eol() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + "{eol}{open}#[|{close}]#{eol}", + eol = LINE_END, + open = pair.0, + close = pair.1 + ), + "i<backspace>", + format!("{0}#[|{0}]#", LINE_END), + LineFeedHandling::AsIs, + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_auto_pairs_disabled() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test_with_config( + AppBuilder::new().with_config(Config { + editor: helix_view::editor::Config { + auto_pairs: AutoPairConfig::Enable(false), + ..Default::default() + }, + ..Default::default() + }), + ( + format!("{}#[|{}]#{}", pair.0, pair.1, LINE_END), + "i<backspace>", + format!("#[|{}]#{}", pair.1, LINE_END), + ), + ) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_before_multi_code_point_graphemes() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("hello {}#[|👨👩👧👦]# goodbye{}", pair.1, LINE_END), + "i<backspace>", + format!("hello #[|👨👩👧👦]# goodbye{}", LINE_END), + )) + .await?; + + test(( + format!( + "hello {}{}#[|👨👩👧👦]# goodbye{}", + pair.0, pair.1, LINE_END + ), + "i<backspace>", + format!("hello {}#[|👨👩👧👦]# goodbye{}", pair.0, LINE_END), + )) + .await?; + + test(( + format!( + "hello {}#[|{}]#👨👩👧👦 goodbye{}", + pair.0, pair.1, LINE_END + ), + "i<backspace>", + format!("hello #[|👨👩👧👦]# goodbye{}", LINE_END), + )) + .await?; + + test(( + format!( + "hello {}#[|{}👨👩👧👦]# goodbye{}", + pair.0, pair.1, LINE_END + ), + "i<backspace>", + format!("hello #[|👨👩👧👦]# goodbye{}", LINE_END), + )) + .await?; + } + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_at_end_of_document() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(TestCase { + in_text: format!("{}{}{}", LINE_END, pair.0, pair.1), + in_selection: Selection::single(LINE_END.len() + 1, LINE_END.len() + 2), + in_keys: String::from("i<backspace>"), + out_text: String::from(LINE_END), + out_selection: Selection::single(LINE_END.len(), LINE_END.len()), + line_feed_handling: LineFeedHandling::AsIs, + }) + .await?; + + test(TestCase { + in_text: format!("foo{}{}{}", LINE_END, pair.0, pair.1), + in_selection: Selection::single(LINE_END.len() + 4, LINE_END.len() + 5), + in_keys: String::from("i<backspace>"), + out_text: format!("foo{}", LINE_END), + out_selection: Selection::single(3 + LINE_END.len(), 3 + LINE_END.len()), + line_feed_handling: LineFeedHandling::AsIs, + }) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_nested_open_inside_pair() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!( + "{open}{open}#[|{close}]#{close}{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + "i<backspace>", + format!( + "{open}#[|{close}]#{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_nested_open_inside_pair_multi() -> anyhow::Result<()> { + for outer_pair in DEFAULT_PAIRS { + for inner_pair in DEFAULT_PAIRS { + if inner_pair.0 == outer_pair.0 { + continue; + } + + test(( + format!( + "{outer_open}{inner_open}#[|{inner_close}]#{outer_close}{eol}{outer_open}{inner_open}#(|{inner_close})#{outer_close}{eol}{outer_open}{inner_open}#(|{inner_close})#{outer_close}{eol}", + outer_open = outer_pair.0, + outer_close = outer_pair.1, + inner_open = inner_pair.0, + inner_close = inner_pair.1, + eol = LINE_END + ), + "i<backspace>", + format!( + "{outer_open}#[|{outer_close}]#{eol}{outer_open}#(|{outer_close})#{eol}{outer_open}#(|{outer_close})#{eol}", + outer_open = outer_pair.0, + outer_close = outer_pair.1, + eol = LINE_END + ), + )) + .await?; + } + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_append_basic() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + "#[{eol}{open}|]#{close}{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + "a<backspace>", + format!("#[{eol}{eol}|]#", eol = LINE_END), + LineFeedHandling::AsIs, + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_append_multi_range() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + "#[ {open}|]#{close}{eol}#( {open}|)#{close}{eol}#( {open}|)#{close}{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + "a<backspace>", + format!("#[ {eol}|]##( {eol}|)##( {eol}|)#", eol = LINE_END), + LineFeedHandling::AsIs, + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_append_end_of_word() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + "fo#[o{open}|]#{close}{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + "a<backspace>", + format!("fo#[o{}|]#", LINE_END), + LineFeedHandling::AsIs, + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_mixed_dedent() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + indoc! {"\ + bar = {}#[|{}]# + #(|\n)# + foo#(|\n)# + "}, + pair.0, pair.1, + ), + "i<backspace>", + indoc! {"\ + bar = #[|\n]# + #(|\n)# + fo#(|\n)# + "}, + )) + .await?; + + test(( + format!( + indoc! {"\ + bar = {}#[|{}woop]# + #(|word)# + fo#(|o)# + "}, + pair.0, pair.1, + ), + "i<backspace>", + indoc! {"\ + bar = #[|woop]# + #(|word)# + f#(|o)# + "}, + )) + .await?; + + // delete from the right with append + test(( + format!( + indoc! {"\ + bar = #[|woop{}]#{} + #(| )#word + #(|fo)#o + "}, + pair.0, pair.1, + ), + "a<backspace>", + indoc! {"\ + bar = #[woop\n|]# + #(w|)#ord + #(fo|)# + "}, + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_append_end_of_word_multi() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + "fo#[o{open}|]#{close}{eol}fo#(o{open}|)#{close}{eol}fo#(o{open}|)#{close}{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + "a<backspace>", + format!("fo#[o{eol}|]#fo#(o{eol}|)#fo#(o{eol}|)#", eol = LINE_END), + LineFeedHandling::AsIs, + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_append_inside_nested_pair() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + "f#[oo{open}{open}|]#{close}{close}{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + "a<backspace>", + format!( + "f#[oo{open}{close}|]#{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_append_middle_of_word() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + "f#[oo{open}{open}|]#{close}{close}{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + "a<backspace>", + format!( + "f#[oo{open}{close}|]#{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn delete_append_inside_nested_pair_multi() -> anyhow::Result<()> { + for outer_pair in DEFAULT_PAIRS { + for inner_pair in DEFAULT_PAIRS { + if inner_pair.0 == outer_pair.0 { + continue; + } + + test(( + format!( + "f#[oo{outer_open}{inner_open}|]#{inner_close}{outer_close}{eol}f#(oo{outer_open}{inner_open}|)#{inner_close}{outer_close}{eol}f#(oo{outer_open}{inner_open}|)#{inner_close}{outer_close}{eol}", + outer_open = outer_pair.0, + outer_close = outer_pair.1, + inner_open = inner_pair.0, + inner_close = inner_pair.1, + eol = LINE_END + ), + "a<backspace>", + format!( + "f#[oo{outer_open}{outer_close}|]#{eol}f#(oo{outer_open}{outer_close}|)#{eol}f#(oo{outer_open}{outer_close}|)#{eol}", + outer_open = outer_pair.0, + outer_close = outer_pair.1, + eol = LINE_END + ), + )) + .await?; + } + } + + Ok(()) +} |