Files
helix/helix-term/tests/test/commands/insert.rs
Matouš DzivjakandGitHub 416a0e0983 fix: continue comment with injected comment syntax (#15940)
Noticed in Go which injects `comment` syntax for comments.
The comment continuation wouldn't work because it would try to
resolve the comment continuation token from the injected comment
syntax which doesn't define one.

Resolved by bubbling up until we find a first layer that defines comment
token.
2026-07-03 10:07:29 +09:00

692 lines
16 KiB
Rust

use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn change_line_above_comment() -> anyhow::Result<()> {
// <https://github.com/helix-editor/helix/issues/12570>
test((
indoc! {"\
#[fn main() {}
|]#// a comment
"},
":lang rust<ret>c",
indoc! {"\
#[
|]#// a comment
"},
))
.await?;
Ok(())
}
#[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")]
async fn insert_newline_trim_trailing_whitespace() -> anyhow::Result<()> {
// Trailing whitespace is trimmed.
test((
indoc! {"\
hello·······#[|
]#world
"}
.replace('·', " "),
"i<ret>",
indoc! {"\
hello
#[|
]#world
"}
.replace('·', " "),
))
.await?;
// Whitespace that would become trailing is trimmed too.
test((
indoc! {"\
hello········#[|w]#orld
"}
.replace('·', " "),
"i<ret>",
indoc! {"\
hello
#[|w]#orld
"}
.replace('·', " "),
))
.await?;
// Only whitespace before the cursor is trimmed.
test((
indoc! {"\
hello········#[|·]#····world
"}
.replace('·', " "),
"i<ret>",
indoc! {"\
hello
#[|·]#····world
"}
.replace('·', " "),
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn insert_newline_trim_whitespace_to_previous_selection() -> anyhow::Result<()> {
test((
indoc! {"\"#[a|]# #(a|)# #(a|)#\""},
"c<ret>",
indoc! {"\"\n#[\n|]##(\n|)##(\"|)#"},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn insert_newline_continue_line_comment() -> anyhow::Result<()> {
// `insert_newline` continues a single line comment
test((
indoc! {"\
// Hello world!#[|
]#
"},
":lang rust<ret>i<ret>",
indoc! {"\
// Hello world!
// #[|
]#
"},
))
.await?;
// Continuation should use the enclosing layer's comment tokens if an injected layer
// doesn't define comment token.
test((
indoc! {"\
// Hello world!#[|
]#
"},
":lang go<ret>i<ret>",
indoc! {"\
// Hello world!
// #[|
]#
"},
))
.await?;
test((
indoc! {"\
//go:generate echo hello#[|
]#
"},
":lang go<ret>i<ret>",
indoc! {"\
//go:generate echo hello
// #[|
]#
"},
))
.await?;
// The comment is not continued if the cursor is before the comment token. (Note that we
// are entering insert-mode with `I`.)
test((
indoc! {"\
// Hello world!#[|
]#
"},
":lang rust<ret>I<ret>",
indoc! {"\
\n#[/|]#/ Hello world!
"},
))
.await?;
// `insert_newline` again clears the whitespace on the first continued comment and continues
// the comment again.
test((
indoc! {"\
// Hello world!
// #[|
]#
"},
":lang rust<ret>i<ret>",
indoc! {"\
// Hello world!
//
// #[|
]#
"},
))
.await?;
// Line comment continuation and trailing whitespace is also trimmed when using
// `insert_newline` in the middle of a comment.
test((
indoc! {"\
//·hello····#[|·]#····world
"}
.replace('·', " "),
":lang rust<ret>i<ret>",
indoc! {"\
//·hello
//·#[|·]#····world
"}
.replace('·', " "),
))
.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(())
}
/// NOTE: Language is set to markdown to check if the indentation is correct for the new line
#[tokio::test(flavor = "multi_thread")]
async fn test_open_above() -> anyhow::Result<()> {
// `O` is pressed in the first line
test((
indoc! {"Helix #[is|]# cool"},
":lang markdown<ret>O",
indoc! {"\
#[\n|]#
Helix is cool
"},
))
.await?;
// `O` is pressed in the first line, but the current line has some indentation
test((
indoc! {"\
··This line has 2 spaces in front of it#[\n|]#
"}
.replace('·', " "),
":lang markdown<ret>Oa",
indoc! {"\
··a#[\n|]#
··This line has 2 spaces in front of it
"}
.replace('·', " "),
))
.await?;
// `O` is pressed but *not* in the first line
test((
indoc! {"\
I use
b#[t|]#w.
"},
":lang markdown<ret>Oarch",
indoc! {"\
I use
arch#[\n|]#
btw.
"},
))
.await?;
// `O` is pressed but *not* in the first line and the line has some indentation
test((
indoc! {"\
I use
····b#[t|]#w.
"}
.replace("·", " "),
":lang markdown<ret>Ohelix",
indoc! {"\
I use
····helix#[\n|]#
····btw.
"}
.replace("·", " "),
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_open_above_with_multiple_cursors() -> anyhow::Result<()> {
// the primary cursor is also in the top line
test((
indoc! {"#[H|]#elix
#(i|)#s
#(c|)#ool"},
"O",
indoc! {
"#[\n|]#
Helix
#(\n|)#
is
#(\n|)#
cool
"
},
))
.await?;
// now with some additional indentation
test((
indoc! {"····#[H|]#elix
····#(i|)#s
····#(c|)#ool"}
.replace("·", " "),
":indent-style 4<ret>O",
indoc! {
"····#[\n|]#
····Helix
····#(\n|)#
····is
····#(\n|)#
····cool
"
}
.replace("·", " "),
))
.await?;
// the first line is within a comment, the second not.
// However, if we open above, the first newly added line should start within a comment
// while the other should be a normal line
test((
indoc! {"fn main() {
// #[VIP|]# comment
l#(e|)#t yes = false;
}"},
":lang rust<ret>O",
indoc! {"fn main() {
// #[\n|]#
// VIP comment
#(\n|)#
let yes = false;
}"},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_open_below_with_multiple_cursors() -> anyhow::Result<()> {
// the primary cursor is also in the top line
test((
indoc! {"#[H|]#elix
#(i|)#s
#(c|)#ool"},
"o",
indoc! {"Helix
#[\n|]#
is
#(\n|)#
cool
#(\n|)#
"
},
))
.await?;
// now with some additional indentation
test((
indoc! {"····#[H|]#elix
····#(i|)#s
····#(c|)#ool"}
.replace("·", " "),
":indent-style 4<ret>o",
indoc! {
"····Helix
····#[\n|]#
····is
····#(\n|)#
····cool
····#(\n|)#
"
}
.replace("·", " "),
))
.await?;
// the first line is within a comment, the second not.
// However, if we open below, the first newly added line should start within a comment
// while the other should be a normal line
test((
indoc! {"fn main() {
// #[VIP|]# comment
l#(e|)#t yes = false;
}"},
":lang rust<ret>o",
indoc! {"fn main() {
// VIP comment
// #[\n|]#
let yes = false;
#(\n|)#
}"},
))
.await?;
// Open below should continue comment even if language has `comment` grammar
// injection.
test((
indoc! {"\
package main
// VIP#[|]# comment
func main() {}
"},
":lang go<ret>o",
indoc! {"\
package main
// VIP comment
// #[\n|]#
func main() {}
"},
))
.await?;
Ok(())
}
/// NOTE: To make the `open_above` comment-aware, we're setting the language for each test to rust.
#[tokio::test(flavor = "multi_thread")]
async fn test_open_above_with_comments() -> anyhow::Result<()> {
// `O` is pressed in the first line inside a line comment
test((
indoc! {"// a commen#[t|]#"},
":lang rust<ret>O",
indoc! {"\
// #[\n|]#
// a comment
"},
))
.await?;
// `O` is pressed in the first line inside a line comment, but with indentation
test((
indoc! {"····// a comm#[e|]#nt"}.replace("·", " "),
":lang rust<ret>O",
indoc! {"\
····// #[\n|]#
····// a comment
"}
.replace("·", " "),
))
.await?;
// `O` is pressed but not in the first line but inside a line comment
test((
indoc! {"\
fn main() { }
// yeetus deletus#[\n|]#
"},
":lang rust<ret>O",
indoc! {"\
fn main() { }
// #[\n|]#
// yeetus deletus
"},
))
.await?;
// `O` is pressed but not in the first line but inside a line comment and with indentation
test((
indoc! {"\
fn main() { }
····// yeetus deletus#[\n|]#
"}
.replace("·", " "),
":lang rust<ret>O",
indoc! {"\
fn main() { }
····// #[\n|]#
····// yeetus deletus
"}
.replace("·", " "),
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn try_restore_indent() -> anyhow::Result<()> {
// Assert that `helix_view::editor::try_restore_indent` handles line endings correctly
// endings.
test((
indoc! {"\
if true #[|{]#
}
"},
// `try_restore_indent` should remove the indentation when adding a blank line.
":lang rust<ret>o<esc>",
indoc! {"\
if true {
#[
|]#}
"},
))
.await?;
Ok(())
}
// Tests being able to jump in insert mode, then undo the write performed by the jump
// https://github.com/helix-editor/helix/issues/13480
#[tokio::test(flavor = "multi_thread")]
async fn test_jump_undo_redo() -> anyhow::Result<()> {
use helix_core::hashmap;
use helix_term::keymap;
use helix_view::document::Mode;
let mut config = Config::default();
config.keys.insert(
Mode::Insert,
keymap!({"Insert Mode"
"C-i" => goto_file_start,
"C-o" => goto_file_end,
}),
);
// Undo
test_with_config(
AppBuilder::new().with_config(config.clone()),
("#[|]#", "iworld<C-i>Hello, <esc>u", "#[w|]#orld"),
)
.await?;
// Redo
test_with_config(
AppBuilder::new().with_config(config),
(
"#[|]#",
"iworld<C-i>Hello, <esc>ui<C-o><esc>U",
"Hello, #[w|]#orld",
),
)
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_indent_with_spaces() -> anyhow::Result<()> {
let tests = vec![
// at start of line
(
indoc! {"\
SELECT *
#[|FROM table]#
#(|WHERE condition)#
"},
"i<tab>",
indoc! {"\
SELECT *
#[|FROM table]#
#(|WHERE condition)#
"},
),
// in the middle of line
(
indoc! {"\
SELECT #[*|]#
FROM #(table|)#
WHERE #(condition|)#
"},
"i<S-tab>",
indoc! {"\
SELECT #[|*]#
FROM #(|table)#
WHERE #(|condition)#
"},
),
// indentation in normal mode
(
indoc! {"\
-- comment
#[|SELECT *
FROM table
WHERE condition]#
"},
"<gt>",
indoc! {"\
-- comment
#[|SELECT *
FROM table
WHERE condition]#
"},
),
];
for test in tests {
test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?;
}
Ok(())
}