Test rendering of snippets

Had a missing ':' between the snippet index and placeholder text
This commit is contained in:
DropDemBits 2023-07-12 17:22:02 -04:00
parent a1877df5a5
commit 614987ae71
No known key found for this signature in database
GPG key ID: 7FE02A6C1EDFA075
2 changed files with 489 additions and 13 deletions

View file

@ -133,7 +133,7 @@ impl FromIterator<(FileId, TextEdit)> for SourceChange {
pub struct SnippetEdit(Vec<(u32, TextRange)>);
impl SnippetEdit {
fn new(snippets: Vec<Snippet>) -> Self {
pub fn new(snippets: Vec<Snippet>) -> Self {
let mut snippet_ranges = snippets
.into_iter()
.zip(1..)
@ -157,8 +157,10 @@ impl SnippetEdit {
snippet_ranges.sort_by_key(|(_, range)| range.start());
// Ensure that none of the ranges overlap
let disjoint_ranges =
snippet_ranges.windows(2).all(|ranges| ranges[0].1.end() <= ranges[1].1.start());
let disjoint_ranges = snippet_ranges
.iter()
.zip(snippet_ranges.iter().skip(1))
.all(|((_, left), (_, right))| left.end() <= right.start() || left == right);
stdx::always!(disjoint_ranges);
SnippetEdit(snippet_ranges)
@ -393,7 +395,7 @@ impl From<FileSystemEdit> for SourceChange {
}
}
enum Snippet {
pub enum Snippet {
/// A tabstop snippet (e.g. `$0`).
Tabstop(TextSize),
/// A placeholder snippet (e.g. `${0:placeholder}`).

View file

@ -904,8 +904,8 @@ fn merge_text_and_snippet_edits(
for (snippet_index, snippet_range) in
snippets.take_while_ref(|(_, range)| range.end() < new_range.start())
{
let snippet_range = if stdx::never!(
!snippet_range.is_empty(),
let snippet_range = if !stdx::always!(
snippet_range.is_empty(),
"placeholder range {:?} is before current text edit range {:?}",
snippet_range,
new_range
@ -957,7 +957,7 @@ fn merge_text_and_snippet_edits(
text_edit.new_text.insert_str(start, &format!("${index}"));
} else {
text_edit.new_text.insert(end, '}');
text_edit.new_text.insert_str(start, &format!("${{{index}"));
text_edit.new_text.insert_str(start, &format!("${{{index}:"));
}
}
@ -974,13 +974,10 @@ fn merge_text_and_snippet_edits(
}
}
// insert any remaining edits
// either one of the two or both should've run out at this point,
// so it's either a tail of text edits or tabstops
edits.extend(text_edits.map(|indel| snippet_text_edit(line_index, false, indel)));
// insert any remaining tabstops
edits.extend(snippets.map(|(snippet_index, snippet_range)| {
let snippet_range = if stdx::never!(
!snippet_range.is_empty(),
let snippet_range = if !stdx::always!(
snippet_range.is_empty(),
"found placeholder snippet {:?} without a text edit",
snippet_range
) {
@ -1542,7 +1539,9 @@ pub(crate) fn rename_error(err: RenameError) -> crate::LspError {
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use ide::{Analysis, FilePosition};
use ide_db::source_change::Snippet;
use test_utils::extract_offset;
use triomphe::Arc;
@ -1612,6 +1611,481 @@ fn bar(_: usize) {}
assert!(!docs.contains("use crate::bar"));
}
fn check_rendered_snippets(edit: TextEdit, snippets: SnippetEdit, expect: Expect) {
let text = r#"/* place to put all ranges in */"#;
let line_index = LineIndex {
index: Arc::new(ide::LineIndex::new(text)),
endings: LineEndings::Unix,
encoding: PositionEncoding::Utf8,
};
let res = merge_text_and_snippet_edits(&line_index, edit, snippets);
expect.assert_debug_eq(&res);
}
#[test]
fn snippet_rendering_only_tabstops() {
let edit = TextEdit::builder().finish();
let snippets = SnippetEdit::new(vec![
Snippet::Tabstop(0.into()),
Snippet::Tabstop(0.into()),
Snippet::Tabstop(1.into()),
Snippet::Tabstop(1.into()),
]);
check_rendered_snippets(
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
new_text: "$1",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
new_text: "$2",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 1,
},
end: Position {
line: 0,
character: 1,
},
},
new_text: "$3",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 1,
},
end: Position {
line: 0,
character: 1,
},
},
new_text: "$0",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
]
"#]],
);
}
#[test]
fn snippet_rendering_only_text_edits() {
let mut edit = TextEdit::builder();
edit.insert(0.into(), "abc".to_owned());
edit.insert(3.into(), "def".to_owned());
let edit = edit.finish();
let snippets = SnippetEdit::new(vec![]);
check_rendered_snippets(
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
new_text: "abc",
insert_text_format: None,
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 3,
},
end: Position {
line: 0,
character: 3,
},
},
new_text: "def",
insert_text_format: None,
annotation_id: None,
},
]
"#]],
);
}
#[test]
fn snippet_rendering_tabstop_after_text_edit() {
let mut edit = TextEdit::builder();
edit.insert(0.into(), "abc".to_owned());
let edit = edit.finish();
let snippets = SnippetEdit::new(vec![Snippet::Tabstop(7.into())]);
check_rendered_snippets(
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
new_text: "abc",
insert_text_format: None,
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 7,
},
end: Position {
line: 0,
character: 7,
},
},
new_text: "$0",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
]
"#]],
);
}
#[test]
fn snippet_rendering_tabstops_before_text_edit() {
let mut edit = TextEdit::builder();
edit.insert(2.into(), "abc".to_owned());
let edit = edit.finish();
let snippets =
SnippetEdit::new(vec![Snippet::Tabstop(0.into()), Snippet::Tabstop(0.into())]);
check_rendered_snippets(
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
new_text: "$1",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
new_text: "$0",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 2,
},
end: Position {
line: 0,
character: 2,
},
},
new_text: "abc",
insert_text_format: None,
annotation_id: None,
},
]
"#]],
);
}
#[test]
fn snippet_rendering_tabstops_between_text_edits() {
let mut edit = TextEdit::builder();
edit.insert(0.into(), "abc".to_owned());
edit.insert(7.into(), "abc".to_owned());
let edit = edit.finish();
let snippets =
SnippetEdit::new(vec![Snippet::Tabstop(4.into()), Snippet::Tabstop(4.into())]);
check_rendered_snippets(
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
new_text: "abc",
insert_text_format: None,
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 4,
},
end: Position {
line: 0,
character: 4,
},
},
new_text: "$1",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 4,
},
end: Position {
line: 0,
character: 4,
},
},
new_text: "$0",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 7,
},
end: Position {
line: 0,
character: 7,
},
},
new_text: "abc",
insert_text_format: None,
annotation_id: None,
},
]
"#]],
);
}
#[test]
fn snippet_rendering_multiple_tabstops_in_text_edit() {
let mut edit = TextEdit::builder();
edit.insert(0.into(), "abcdefghijkl".to_owned());
let edit = edit.finish();
let snippets = SnippetEdit::new(vec![
Snippet::Tabstop(0.into()),
Snippet::Tabstop(5.into()),
Snippet::Tabstop(12.into()),
]);
check_rendered_snippets(
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
new_text: "$1abcde$2fghijkl$0",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
]
"#]],
);
}
#[test]
fn snippet_rendering_multiple_placeholders_in_text_edit() {
let mut edit = TextEdit::builder();
edit.insert(0.into(), "abcdefghijkl".to_owned());
let edit = edit.finish();
let snippets = SnippetEdit::new(vec![
Snippet::Placeholder(TextRange::new(0.into(), 3.into())),
Snippet::Placeholder(TextRange::new(5.into(), 7.into())),
Snippet::Placeholder(TextRange::new(10.into(), 12.into())),
]);
check_rendered_snippets(
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
new_text: "${1:abc}de${2:fg}hij${0:kl}",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
]
"#]],
);
}
#[test]
fn snippet_rendering_escape_snippet_bits() {
// only needed for snippet formats
let mut edit = TextEdit::builder();
edit.insert(0.into(), r"abc\def$".to_owned());
edit.insert(8.into(), r"ghi\jkl$".to_owned());
let edit = edit.finish();
let snippets =
SnippetEdit::new(vec![Snippet::Placeholder(TextRange::new(0.into(), 3.into()))]);
check_rendered_snippets(
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
new_text: "${0:abc}\\\\def\\$",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 8,
},
end: Position {
line: 0,
character: 8,
},
},
new_text: "ghi\\jkl$",
insert_text_format: None,
annotation_id: None,
},
]
"#]],
);
}
// `Url` is not able to parse windows paths on unix machines.
#[test]
#[cfg(target_os = "windows")]