mirror of
https://github.com/helix-editor/helix.git
synced 2025-01-18 21:17:08 +04:00
Fix offset tracking in insert_newline
#12177 changed `insert_newline`'s behavior to trim any trailing whitespace on a line which came before a cursor. `insert_newline` would previously never delete text. Even the whitespace stripping behavior in #4854 worked by inserting text - a line ending at the beginning of the line. `global_offs`, a variable that tracks the number of characters inserted between iterations over the existing selection ranges, was not updated to also account for text deleted by the trimming behavior, causing cursors to be offset by the amount of trailing space deleted and causing panics in some cases. To fix this we need to subtract the number of trimmed whitespace characters from `global_offs`. `global_offs` must become an `isize` (was a `usize`) because it may become negative in cases where a lot of trailing whitespace is trimmed. Integration tests have been added for each of these cases. Fixes #12461 Fixes #12495 Fixes #12539
This commit is contained in:
parent
99d33c741a
commit
4bd17e542e
@ -3991,6 +3991,8 @@ pub fn insert_newline(cx: &mut Context) {
|
|||||||
let mut global_offs = 0;
|
let mut global_offs = 0;
|
||||||
|
|
||||||
let mut transaction = Transaction::change_by_selection(contents, &selection, |range| {
|
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 pos = range.cursor(text);
|
||||||
|
|
||||||
let prev = if pos == 0 {
|
let prev = if pos == 0 {
|
||||||
@ -4069,13 +4071,14 @@ pub fn insert_newline(cx: &mut Context) {
|
|||||||
new_text.chars().count()
|
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,
|
first_trailing_whitespace_char,
|
||||||
pos,
|
pos,
|
||||||
// Note that `first_trailing_whitespace_char` is at least `pos` so the
|
local_offs as isize - chars_deleted as isize,
|
||||||
// unsigned subtraction (`pos - first_trailing_whitespace_char`) cannot
|
|
||||||
// underflow.
|
|
||||||
local_offs as isize - (pos - first_trailing_whitespace_char) as isize,
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// If the current line is all whitespace, insert a line ending at the beginning of
|
// 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 {
|
let new_range = if range.cursor(text) > range.anchor {
|
||||||
// when appending, extend the range by local_offs
|
// when appending, extend the range by local_offs
|
||||||
Range::new(
|
Range::new(
|
||||||
range.anchor + global_offs,
|
(range.anchor as isize + global_offs) as usize,
|
||||||
(range.head as isize + local_offs) as usize + global_offs,
|
(range.head as isize + local_offs + global_offs) as usize,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// when inserting, slide the range by local_offs
|
// when inserting, slide the range by local_offs
|
||||||
Range::new(
|
Range::new(
|
||||||
(range.anchor as isize + local_offs) as usize + global_offs,
|
(range.anchor as isize + local_offs + global_offs) as usize,
|
||||||
(range.head as isize + local_offs) as usize + global_offs,
|
(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
|
// 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
|
// can be used with cx.mode to do replace or extend on most changes
|
||||||
ranges.push(new_range);
|
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()))
|
(from, to, Some(new_text.into()))
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,106 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn insert_newline_many_selections() -> anyhow::Result<()> {
|
||||||
|
test((
|
||||||
|
indoc! {"\
|
||||||
|
#(|o)#ne
|
||||||
|
#(|t)#wo
|
||||||
|
#[|t]#hree
|
||||||
|
"},
|
||||||
|
"i<ret>",
|
||||||
|
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<ret>",
|
||||||
|
indoc! {"\
|
||||||
|
\n#[|🏴☠️]#
|
||||||
|
#(|🏴☠️)#
|
||||||
|
#(|🏴☠️)#
|
||||||
|
|
||||||
|
#(|🏴☠️)#
|
||||||
|
#(|🏴☠️)#
|
||||||
|
#(|🏴☠️)#
|
||||||
|
"},
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// <https://github.com/helix-editor/helix/issues/12495>
|
||||||
|
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<ret>",
|
||||||
|
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?;
|
||||||
|
|
||||||
|
// <https://github.com/helix-editor/helix/issues/12461>
|
||||||
|
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<ret>",
|
||||||
|
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")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn insert_newline_trim_trailing_whitespace() -> anyhow::Result<()> {
|
async fn insert_newline_trim_trailing_whitespace() -> anyhow::Result<()> {
|
||||||
// Trailing whitespace is trimmed.
|
// Trailing whitespace is trimmed.
|
||||||
@ -117,6 +218,33 @@ async fn insert_newline_continue_line_comment() -> anyhow::Result<()> {
|
|||||||
))
|
))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// Comment continuation should work on multiple selections.
|
||||||
|
// <https://github.com/helix-editor/helix/issues/12539>
|
||||||
|
test((
|
||||||
|
indoc! {"\
|
||||||
|
///·Docs#[|·]#
|
||||||
|
pub·struct·A;
|
||||||
|
|
||||||
|
///·Docs#(|·)#
|
||||||
|
pub·struct·B;
|
||||||
|
"}
|
||||||
|
.replace('·', " "),
|
||||||
|
":lang rust<ret>i<ret><ret>",
|
||||||
|
indoc! {"\
|
||||||
|
///·Docs
|
||||||
|
///
|
||||||
|
///·#[|·]#
|
||||||
|
pub·struct·A;
|
||||||
|
|
||||||
|
///·Docs
|
||||||
|
///
|
||||||
|
///·#(|·)#
|
||||||
|
pub·struct·B;
|
||||||
|
"}
|
||||||
|
.replace('·', " "),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user