mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-11-26 22:03:11 +00:00
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:
commit
ac998a74b3
1 changed files with 205 additions and 142 deletions
|
|
@ -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")]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue