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