Auto merge of #16579 - DropDemBits:structured-snippet-fix-with-escaped-bits-and-cr, r=Veykril

fix: Fix snippets being placed leftwards of where they should be

Snippet bits were being escaped before placing snippets, shifting snippets leftwards. Snippets were also being shifted leftwards on files with CRLF line endings since they were placed done after the Unix -> DOS line ending conversion.

Hoping this fixes all of the little bugs related to snippet rendering 😅
This commit is contained in:
bors 2024-02-16 18:45:43 +00:00
commit ac998a74b3

View File

@ -971,15 +971,11 @@ fn merge_text_and_snippet_edits(
snippet_range
};
let range = range(line_index, snippet_range);
let new_text = format!("${snippet_index}");
edits.push(SnippetTextEdit {
range,
new_text,
insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
annotation_id: None,
})
edits.push(snippet_text_edit(
line_index,
true,
Indel { insert: format!("${snippet_index}"), delete: snippet_range },
))
}
if snippets.peek().is_some_and(|(_, range)| {
@ -1002,31 +998,44 @@ fn merge_text_and_snippet_edits(
)
});
let mut text_edit = text_edit(line_index, current_indel);
let mut new_text = current_indel.insert;
// escape out snippet text
stdx::replace(&mut text_edit.new_text, '\\', r"\\");
stdx::replace(&mut text_edit.new_text, '$', r"\$");
// find which snippet bits need to be escaped
let escape_places = new_text
.rmatch_indices(['\\', '$', '{', '}'])
.map(|(insert, _)| insert)
.collect_vec();
let mut escape_places = escape_places.into_iter().peekable();
let mut escape_prior_bits = |new_text: &mut String, up_to: usize| {
for before in escape_places.peeking_take_while(|insert| *insert >= up_to) {
new_text.insert(before, '\\');
}
};
// ...and apply!
// insert snippets, and escaping any needed bits along the way
for (index, range) in all_snippets.iter().rev() {
let start = (range.start() - new_range.start()).into();
let end = (range.end() - new_range.start()).into();
let text_range = range - new_range.start();
let (start, end) = (text_range.start().into(), text_range.end().into());
if range.is_empty() {
text_edit.new_text.insert_str(start, &format!("${index}"));
escape_prior_bits(&mut new_text, start);
new_text.insert_str(start, &format!("${index}"));
} else {
text_edit.new_text.insert(end, '}');
text_edit.new_text.insert_str(start, &format!("${{{index}:"));
escape_prior_bits(&mut new_text, end);
new_text.insert(end, '}');
escape_prior_bits(&mut new_text, start);
new_text.insert_str(start, &format!("${{{index}:"));
}
}
edits.push(SnippetTextEdit {
range: text_edit.range,
new_text: text_edit.new_text,
insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
annotation_id: None,
})
// escape any remaining bits
escape_prior_bits(&mut new_text, 0);
edits.push(snippet_text_edit(
line_index,
true,
Indel { insert: new_text, delete: current_indel.delete },
))
} else {
// snippet edit was beyond the current one
// since it wasn't consumed, it's available for the next pass
@ -1052,15 +1061,11 @@ fn merge_text_and_snippet_edits(
snippet_range
};
let range = range(line_index, snippet_range);
let new_text = format!("${snippet_index}");
SnippetTextEdit {
range,
new_text,
insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
annotation_id: None,
}
snippet_text_edit(
line_index,
true,
Indel { insert: format!("${snippet_index}"), delete: snippet_range },
)
}));
edits
@ -1705,9 +1710,10 @@ fn bar(_: usize) {}
expect: Expect,
) {
let source = stdx::trim_indent(ra_fixture);
let endings = if source.contains('\r') { LineEndings::Dos } else { LineEndings::Unix };
let line_index = LineIndex {
index: Arc::new(ide::LineIndex::new(&source)),
endings: LineEndings::Unix,
endings,
encoding: PositionEncoding::Utf8,
};
@ -2144,51 +2150,71 @@ fn bar(_: usize) {}
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());
edit.insert(0.into(), r"$ab{}$c\def".to_owned());
edit.insert(8.into(), r"ghi\jk<-check_insert_here$".to_owned());
edit.insert(10.into(), r"a\\b\\c{}$".to_owned());
let edit = edit.finish();
let snippets =
SnippetEdit::new(vec![Snippet::Placeholder(TextRange::new(0.into(), 3.into()))]);
let snippets = SnippetEdit::new(vec![
Snippet::Placeholder(TextRange::new(1.into(), 9.into())),
Snippet::Tabstop(25.into()),
]);
check_rendered_snippets(
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
[
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
new_text: "\\$${1:ab\\{\\}\\$c\\\\d}ef",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
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,
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 8,
},
end: Position {
line: 0,
character: 8,
},
},
new_text: "ghi\\\\jk$0<-check_insert_here\\$",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
new_text: "ghi\\jkl$",
insert_text_format: None,
annotation_id: None,
},
]
"#]],
SnippetTextEdit {
range: Range {
start: Position {
line: 0,
character: 10,
},
end: Position {
line: 0,
character: 10,
},
},
new_text: "a\\\\b\\\\c{}$",
insert_text_format: None,
annotation_id: None,
},
]
"#]],
);
}
@ -2218,41 +2244,41 @@ struct ProcMacro {
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 1,
character: 4,
},
end: Position {
line: 1,
character: 13,
},
},
new_text: "let",
insert_text_format: None,
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 1,
character: 14,
},
end: Position {
line: 3,
character: 5,
},
},
new_text: "$0disabled = false;\n ProcMacro {\n disabled,\n }",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
]
"#]],
[
SnippetTextEdit {
range: Range {
start: Position {
line: 1,
character: 4,
},
end: Position {
line: 1,
character: 13,
},
},
new_text: "let",
insert_text_format: None,
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 1,
character: 14,
},
end: Position {
line: 3,
character: 5,
},
},
new_text: "$0disabled = false;\n ProcMacro \\{\n disabled,\n \\}",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
]
"#]],
);
}
@ -2282,41 +2308,41 @@ struct P {
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 1,
character: 4,
},
end: Position {
line: 1,
character: 5,
},
},
new_text: "let",
insert_text_format: None,
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 1,
character: 6,
},
end: Position {
line: 3,
character: 5,
},
},
new_text: "$0disabled = false;\n ProcMacro {\n disabled,\n }",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
]
"#]],
[
SnippetTextEdit {
range: Range {
start: Position {
line: 1,
character: 4,
},
end: Position {
line: 1,
character: 5,
},
},
new_text: "let",
insert_text_format: None,
annotation_id: None,
},
SnippetTextEdit {
range: Range {
start: Position {
line: 1,
character: 6,
},
end: Position {
line: 3,
character: 5,
},
},
new_text: "$0disabled = false;\n ProcMacro \\{\n disabled,\n \\}",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
]
"#]],
);
}
@ -2374,7 +2400,7 @@ struct ProcMacro {
character: 5,
},
},
new_text: "${0:disabled} = false;\n ProcMacro {\n disabled,\n }",
new_text: "${0:disabled} = false;\n ProcMacro \\{\n disabled,\n \\}",
insert_text_format: Some(
Snippet,
),
@ -2439,7 +2465,7 @@ struct P {
character: 5,
},
},
new_text: "${0:disabled} = false;\n ProcMacro {\n disabled,\n }",
new_text: "${0:disabled} = false;\n ProcMacro \\{\n disabled,\n \\}",
insert_text_format: Some(
Snippet,
),
@ -2609,6 +2635,43 @@ struct ProcMacro {
);
}
#[test]
fn snippet_rendering_handle_dos_line_endings() {
// unix -> dos conversion should be handled after placing snippets
let mut edit = TextEdit::builder();
edit.insert(6.into(), "\n\n->".to_owned());
let edit = edit.finish();
let snippets = SnippetEdit::new(vec![Snippet::Tabstop(10.into())]);
check_rendered_snippets_in_source(
"yeah\r\n<-tabstop here",
edit,
snippets,
expect![[r#"
[
SnippetTextEdit {
range: Range {
start: Position {
line: 1,
character: 0,
},
end: Position {
line: 1,
character: 0,
},
},
new_text: "\r\n\r\n->$0",
insert_text_format: Some(
Snippet,
),
annotation_id: None,
},
]
"#]],
)
}
// `Url` is not able to parse windows paths on unix machines.
#[test]
#[cfg(target_os = "windows")]