From 6a0b450f55675c76d67bfb026caa2df4b601153b Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Thu, 20 Oct 2022 20:22:20 -0400 Subject: Fix multi byte auto pairs (#4024) * Fix test::print for Unicode The print function was not generating correct translations when the input has Unicode (non-ASCII) in it. This is due to its use of String::len, which gives the length in bytes, not chars. * Fix multi-code point auto pairs The current code for auto pairs is counting offsets by summing the length of the open and closing chars with char::len_utf8. Unfortunately, this gives back bytes, and the offset needs to be in chars. Additionally, it was discovered that there was a preexisting bug where the selection was not computed correctly in the case that the cursor was: 1. a single grapheme in width 2. this grapheme was more than one char 3. the direction of the cursor is backwards 4. a secondary range In this case, the offset was not being added into the anchor. This was fixed. * migrate auto pairs tests to integration * review comments--- helix-term/tests/test/auto_pairs.rs | 551 +++++++++++++++++++++++++++++++++++- 1 file changed, 538 insertions(+), 13 deletions(-) (limited to 'helix-term/tests/test/auto_pairs.rs') diff --git a/helix-term/tests/test/auto_pairs.rs b/helix-term/tests/test/auto_pairs.rs index caf80bd4..f2ab49c7 100644 --- a/helix-term/tests/test/auto_pairs.rs +++ b/helix-term/tests/test/auto_pairs.rs @@ -1,22 +1,547 @@ +use helix_core::{auto_pairs::DEFAULT_PAIRS, hashmap}; + use super::*; +const LINE_END: &str = helix_core::DEFAULT_LINE_ENDING.as_str(); + +fn differing_pairs() -> impl Iterator { + DEFAULT_PAIRS.iter().filter(|(open, close)| open != close) +} + +fn matching_pairs() -> impl Iterator { + DEFAULT_PAIRS.iter().filter(|(open, close)| open == close) +} + #[tokio::test] -async fn auto_pairs_basic() -> anyhow::Result<()> { - test(("#[\n|]#", "i(", "(#[|)]#\n")).await?; +async fn insert_basic() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("#[{}|]#", LINE_END), + format!("i{}", pair.0), + format!("{}#[|{}]#{}", pair.0, pair.1, LINE_END), + )) + .await?; + } - test_with_config( - Args::default(), - Config { - editor: helix_view::editor::Config { - auto_pairs: AutoPairConfig::Enable(false), - ..Default::default() - }, + Ok(()) +} + +#[tokio::test] +async fn insert_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() }, - helpers::test_syntax_conf(None), - ("#[\n|]#", "i(", "(#[|\n]#"), - ) - .await?; + ..Default::default() + }; + + for (open, close) in pairs.iter() { + test_with_config( + Args::default(), + config.clone(), + helpers::test_syntax_conf(None), + ( + format!("#[{}|]#", LINE_END), + format!("i{}", open), + format!("{}#[|{}]#{}", open, close, LINE_END), + ), + ) + .await?; + + test_with_config( + Args::default(), + config.clone(), + helpers::test_syntax_conf(None), + ( + format!("{}#[{}|]#{}", open, close, LINE_END), + format!("i{}", close), + format!("{}{}#[|{}]#", open, close, LINE_END), + ), + ) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_after_word() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!("foo#[{}|]#", LINE_END), + format!("i{}", pair.0), + format!("foo{}#[|{}]#{}", pair.0, pair.1, LINE_END), + )) + .await?; + } + + for pair in matching_pairs() { + test(( + format!("foo#[{}|]#", LINE_END), + format!("i{}", pair.0), + format!("foo{}#[|{}]#", pair.0, LINE_END), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_before_word() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("#[f|]#oo{}", LINE_END), + format!("i{}", pair.0), + format!("{}#[|f]#oo{}", pair.0, LINE_END), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_before_word_selection() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("#[foo|]#{}", LINE_END), + format!("i{}", pair.0), + format!("{}#[|foo]#{}", pair.0, LINE_END), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_before_word_selection_trailing_word() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!("foo#[ wor|]#{}", LINE_END), + format!("i{}", pair.0), + format!("foo{}#[|{} wor]#{}", pair.0, pair.1, LINE_END), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_closer_selection_trailing_word() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!("foo{}#[|{} wor]#{}", pair.0, pair.1, LINE_END), + format!("i{}", pair.1), + format!("foo{}{}#[| wor]#{}", pair.0, pair.1, LINE_END), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_before_eol() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("{0}#[{0}|]#", LINE_END), + format!("i{}", pair.0), + format!( + "{eol}{open}#[|{close}]#{eol}", + eol = LINE_END, + open = pair.0, + close = pair.1 + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_auto_pairs_disabled() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test_with_config( + Args::default(), + Config { + editor: helix_view::editor::Config { + auto_pairs: AutoPairConfig::Enable(false), + ..Default::default() + }, + ..Default::default() + }, + helpers::test_syntax_conf(None), + ( + format!("#[{}|]#", LINE_END), + format!("i{}", pair.0), + format!("{}#[|{}]#", pair.0, LINE_END), + ), + ) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_multi_range() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("#[{eol}|]##({eol}|)##({eol}|)#", eol = LINE_END), + format!("i{}", pair.0), + format!( + "{open}#[|{close}]#{eol}{open}#(|{close})#{eol}{open}#(|{close})#{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_before_multi_code_point_graphemes() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!("hello #[👨‍👩‍👧‍👦|]# goodbye{}", LINE_END), + format!("i{}", pair.1), + format!("hello {}#[|👨‍👩‍👧‍👦]# goodbye{}", pair.1, LINE_END), + )) + .await?; + } + Ok(()) +} + +#[tokio::test] +async fn insert_at_end_of_document() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(TestCase { + in_text: String::from(LINE_END), + in_selection: Selection::single(LINE_END.len(), LINE_END.len()), + in_keys: format!("i{}", pair.0), + out_text: format!("{}{}{}", LINE_END, pair.0, pair.1), + out_selection: Selection::single(LINE_END.len() + 1, LINE_END.len() + 2), + }) + .await?; + + test(TestCase { + in_text: format!("foo{}", LINE_END), + in_selection: Selection::single(3 + LINE_END.len(), 3 + LINE_END.len()), + in_keys: format!("i{}", pair.0), + out_text: format!("foo{}{}{}", LINE_END, pair.0, pair.1), + out_selection: Selection::single(LINE_END.len() + 4, LINE_END.len() + 5), + }) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_close_inside_pair() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + "{open}#[{close}|]#{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + format!("i{}", pair.1), + format!( + "{open}{close}#[|{eol}]#", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_close_inside_pair_multi() -> 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 + ), + format!("i{}", pair.1), + format!( + "{open}{close}#[|{eol}]#{open}{close}#(|{eol})#{open}{close}#(|{eol})#", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_nested_open_inside_pair() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!( + "{open}#[{close}|]#{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + format!("i{}", pair.0), + format!( + "{open}{open}#[|{close}]#{close}{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn insert_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}#[{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 + ), + format!("i{}", inner_pair.0), + 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 + ), + )) + .await?; + } + } + + Ok(()) +} + +#[tokio::test] +async fn append_basic() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("#[{}|]#", LINE_END), + format!("a{}", pair.0), + format!( + "#[{eol}{open}{close}|]#{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn append_multi_range() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!("#[ |]#{eol}#( |)#{eol}#( |)#{eol}", eol = LINE_END), + format!("a{}", pair.0), + format!( + "#[ {open}{close}|]#{eol}#( {open}{close}|)#{eol}#( {open}{close}|)#{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn append_close_inside_pair() -> anyhow::Result<()> { + for pair in DEFAULT_PAIRS { + test(( + format!( + "#[{open}|]#{close}{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + format!("a{}", pair.1), + format!( + "#[{open}{close}{eol}|]#", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn append_close_inside_pair_multi() -> 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 + ), + format!("a{}", pair.1), + format!( + "#[{open}{close}{eol}|]##({open}{close}{eol}|)##({open}{close}{eol}|)#", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn append_end_of_word() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!("fo#[o|]#{}", LINE_END), + format!("a{}", pair.0), + format!( + "fo#[o{open}{close}|]#{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn append_middle_of_word() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!("#[wo|]#rd{}", LINE_END), + format!("a{}", pair.1), + format!("#[wo{}r|]#d{}", pair.1, LINE_END), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn append_end_of_word_multi() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!("fo#[o|]#{eol}fo#(o|)#{eol}fo#(o|)#{eol}", eol = LINE_END), + format!("a{}", pair.0), + 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 + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn append_inside_nested_pair() -> anyhow::Result<()> { + for pair in differing_pairs() { + test(( + format!( + "f#[oo{open}|]#{close}{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + format!("a{}", pair.0), + format!( + "f#[oo{open}{open}{close}|]#{close}{eol}", + open = pair.0, + close = pair.1, + eol = LINE_END + ), + )) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn 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}|]#{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 + ), + format!("a{}", inner_pair.0), + 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 + ), + )) + .await?; + } + } Ok(()) } -- cgit v1.2.3-70-g09d2