diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 755a7dc07..9ef1fe153 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3991,6 +3991,8 @@ pub fn insert_newline(cx: &mut Context) { let mut global_offs = 0; let mut transaction = Transaction::change_by_selection(contents, &selection, |range| { + // Tracks the number of trailing whitespace characters deleted by this selection. + let mut chars_deleted = 0; let pos = range.cursor(text); let prev = if pos == 0 { @@ -4069,13 +4071,14 @@ pub fn insert_newline(cx: &mut Context) { new_text.chars().count() }; + // Note that `first_trailing_whitespace_char` is at least `pos` so this unsigned + // subtraction cannot underflow. + chars_deleted = pos - first_trailing_whitespace_char; + ( first_trailing_whitespace_char, pos, - // Note that `first_trailing_whitespace_char` is at least `pos` so the - // unsigned subtraction (`pos - first_trailing_whitespace_char`) cannot - // underflow. - local_offs as isize - (pos - first_trailing_whitespace_char) as isize, + local_offs as isize - chars_deleted as isize, ) } else { // If the current line is all whitespace, insert a line ending at the beginning of @@ -4089,14 +4092,14 @@ pub fn insert_newline(cx: &mut Context) { let new_range = if range.cursor(text) > range.anchor { // when appending, extend the range by local_offs Range::new( - range.anchor + global_offs, - (range.head as isize + local_offs) as usize + global_offs, + (range.anchor as isize + global_offs) as usize, + (range.head as isize + local_offs + global_offs) as usize, ) } else { // when inserting, slide the range by local_offs Range::new( - (range.anchor as isize + local_offs) as usize + global_offs, - (range.head as isize + local_offs) as usize + global_offs, + (range.anchor as isize + local_offs + global_offs) as usize, + (range.head as isize + local_offs + global_offs) as usize, ) }; @@ -4104,7 +4107,7 @@ pub fn insert_newline(cx: &mut Context) { // range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos // can be used with cx.mode to do replace or extend on most changes ranges.push(new_range); - global_offs += new_text.chars().count(); + global_offs += new_text.chars().count() as isize - chars_deleted as isize; (from, to, Some(new_text.into())) }); diff --git a/helix-term/tests/test/commands/insert.rs b/helix-term/tests/test/commands/insert.rs index f23876dfb..9352f737f 100644 --- a/helix-term/tests/test/commands/insert.rs +++ b/helix-term/tests/test/commands/insert.rs @@ -1,5 +1,106 @@ use super::*; +#[tokio::test(flavor = "multi_thread")] +async fn insert_newline_many_selections() -> anyhow::Result<()> { + test(( + indoc! {"\ + #(|o)#ne + #(|t)#wo + #[|t]#hree + "}, + "i", + indoc! {"\ + \n#(|o)#ne + + #(|t)#wo + + #[|t]#hree + "}, + )) + .await?; + + // In this case the global offset that adjusts selections for inserted and deleted text + // should become negative because more text is deleted than is inserted. + test(( + indoc! {"\ + #[|🏴‍☠️]# #(|🏴‍☠️)# #(|🏴‍☠️)# + #(|🏴‍☠️)# #(|🏴‍☠️)# #(|🏴‍☠️)# + "}, + "i", + indoc! {"\ + \n#[|🏴‍☠️]# + #(|🏴‍☠️)# + #(|🏴‍☠️)# + + #(|🏴‍☠️)# + #(|🏴‍☠️)# + #(|🏴‍☠️)# + "}, + )) + .await?; + + // + test(( + indoc! {"\ + id #(|1)#,Item #(|1)#,cost #(|1)#,location #(|1)# + id #(|2)#,Item #(|2)#,cost #(|2)#,location #(|2)# + id #(|1)##(|0)#,Item #(|1)##(|0)#,cost #(|1)##(|0)#,location #(|1)##[|0]#"}, + "i", + indoc! {"\ + id + #(|1)#,Item + #(|1)#,cost + #(|1)#,location + #(|1)# + id + #(|2)#,Item + #(|2)#,cost + #(|2)#,location + #(|2)# + id + #(|1)# + #(|0)#,Item + #(|1)# + #(|0)#,cost + #(|1)# + #(|0)#,location + #(|1)# + #[|0]#"}, + )) + .await?; + + // + test(( + indoc! {"\ + real R〉 #(||)# 〈real R〉 @ 〈real R〉 + #(||)# 〈real R〉 + 〈ureal R〉 i #(||)# 〈real R〉 - 〈ureal R〉 i + #(||)# 〈real R〉 + i #(||)# 〈real R〉 - i #(||)# 〈real R〉 〈infnan〉 i + #(||)# + 〈ureal R〉 i #(||)# - 〈ureal R〉 i + #(||)# 〈infnan〉 i #(||)# + i #[||]# - i"}, + "i", + indoc! {"\ + real R〉 + #(||)# 〈real R〉 @ 〈real R〉 + + #(||)# 〈real R〉 + 〈ureal R〉 i + #(||)# 〈real R〉 - 〈ureal R〉 i + + #(||)# 〈real R〉 + i + #(||)# 〈real R〉 - i + #(||)# 〈real R〉 〈infnan〉 i + + #(||)# + 〈ureal R〉 i + #(||)# - 〈ureal R〉 i + + #(||)# 〈infnan〉 i + #(||)# + i + #[||]# - i"}, + )) + .await?; + + Ok(()) +} + #[tokio::test(flavor = "multi_thread")] async fn insert_newline_trim_trailing_whitespace() -> anyhow::Result<()> { // Trailing whitespace is trimmed. @@ -117,6 +218,33 @@ async fn insert_newline_continue_line_comment() -> anyhow::Result<()> { )) .await?; + // Comment continuation should work on multiple selections. + // + test(( + indoc! {"\ + ///·Docs#[|·]# + pub·struct·A; + + ///·Docs#(|·)# + pub·struct·B; + "} + .replace('·', " "), + ":lang rusti", + indoc! {"\ + ///·Docs + /// + ///·#[|·]# + pub·struct·A; + + ///·Docs + /// + ///·#(|·)# + pub·struct·B; + "} + .replace('·', " "), + )) + .await?; + Ok(()) }