mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 06:11:35 +00:00
Merge commit 'baee6b338b
' into sync-from-ra
This commit is contained in:
parent
0155385b57
commit
aa55ce9567
139 changed files with 4248 additions and 1042 deletions
|
@ -128,7 +128,7 @@ impl flags::AnalysisStats {
|
|||
let mut visited_modules = FxHashSet::default();
|
||||
let mut visit_queue = Vec::new();
|
||||
for krate in krates {
|
||||
let module = krate.root_module(db);
|
||||
let module = krate.root_module();
|
||||
let file_id = module.definition_source_file_id(db);
|
||||
let file_id = file_id.original_file(db);
|
||||
let source_root = db.file_source_root(file_id);
|
||||
|
|
|
@ -80,7 +80,7 @@ impl flags::Diagnostics {
|
|||
|
||||
fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
|
||||
let mut worklist: Vec<_> =
|
||||
Crate::all(db).into_iter().map(|krate| krate.root_module(db)).collect();
|
||||
Crate::all(db).into_iter().map(|krate| krate.root_module()).collect();
|
||||
let mut modules = Vec::new();
|
||||
|
||||
while let Some(module) = worklist.pop() {
|
||||
|
|
|
@ -76,7 +76,7 @@ fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
|
|||
let mut worklist: Vec<_> = Crate::all(db)
|
||||
.into_iter()
|
||||
.filter(|x| x.origin(db).is_local())
|
||||
.map(|krate| krate.root_module(db))
|
||||
.map(|krate| krate.root_module())
|
||||
.collect();
|
||||
let mut modules = Vec::new();
|
||||
|
||||
|
|
|
@ -416,6 +416,44 @@ pub mod module {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_for_param() {
|
||||
check_symbol(
|
||||
r#"
|
||||
//- /lib.rs crate:main deps:foo
|
||||
use foo::example_mod::func;
|
||||
fn main() {
|
||||
func(42);
|
||||
}
|
||||
//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
|
||||
pub mod example_mod {
|
||||
pub fn func(x$0: usize) {}
|
||||
}
|
||||
"#,
|
||||
"rust-analyzer cargo foo 0.1.0 example_mod/func().(x)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_for_closure_param() {
|
||||
check_symbol(
|
||||
r#"
|
||||
//- /lib.rs crate:main deps:foo
|
||||
use foo::example_mod::func;
|
||||
fn main() {
|
||||
func();
|
||||
}
|
||||
//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
|
||||
pub mod example_mod {
|
||||
pub fn func() {
|
||||
let f = |x$0: usize| {};
|
||||
}
|
||||
}
|
||||
"#,
|
||||
"rust-analyzer cargo foo 0.1.0 example_mod/func().(x)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_symbol_for_local() {
|
||||
check_symbol(
|
||||
|
|
|
@ -353,7 +353,8 @@ pub(crate) fn handle_on_type_formatting(
|
|||
};
|
||||
|
||||
// This should be a single-file edit
|
||||
let (_, text_edit) = edit.source_file_edits.into_iter().next().unwrap();
|
||||
let (_, (text_edit, snippet_edit)) = edit.source_file_edits.into_iter().next().unwrap();
|
||||
stdx::never!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets");
|
||||
|
||||
let change = to_proto::snippet_text_edit_vec(&line_index, edit.is_snippet, text_edit);
|
||||
Ok(Some(change))
|
||||
|
|
|
@ -114,6 +114,11 @@ impl GlobalState {
|
|||
if self.proc_macro_clients.iter().any(|it| it.is_err()) {
|
||||
status.health = lsp_ext::Health::Warning;
|
||||
message.push_str("Failed to spawn one or more proc-macro servers.\n\n");
|
||||
for err in self.proc_macro_clients.iter() {
|
||||
if let Err(err) = err {
|
||||
format_to!(message, "- {err}\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if !self.config.cargo_autoreload()
|
||||
&& self.is_quiescent()
|
||||
|
|
|
@ -10,8 +10,8 @@ use ide::{
|
|||
CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
|
||||
Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint,
|
||||
InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory,
|
||||
RenameError, Runnable, Severity, SignatureHelp, SourceChange, StructureNodeKind, SymbolKind,
|
||||
TextEdit, TextRange, TextSize,
|
||||
RenameError, Runnable, Severity, SignatureHelp, SnippetEdit, SourceChange, StructureNodeKind,
|
||||
SymbolKind, TextEdit, TextRange, TextSize,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use serde_json::to_value;
|
||||
|
@ -22,7 +22,7 @@ use crate::{
|
|||
config::{CallInfoConfig, Config},
|
||||
global_state::GlobalStateSnapshot,
|
||||
line_index::{LineEndings, LineIndex, PositionEncoding},
|
||||
lsp_ext,
|
||||
lsp_ext::{self, SnippetTextEdit},
|
||||
lsp_utils::invalid_params_error,
|
||||
semantic_tokens::{self, standard_fallback_type},
|
||||
};
|
||||
|
@ -885,16 +885,136 @@ fn outside_workspace_annotation_id() -> String {
|
|||
String::from("OutsideWorkspace")
|
||||
}
|
||||
|
||||
fn merge_text_and_snippet_edits(
|
||||
line_index: &LineIndex,
|
||||
edit: TextEdit,
|
||||
snippet_edit: SnippetEdit,
|
||||
) -> Vec<SnippetTextEdit> {
|
||||
let mut edits: Vec<SnippetTextEdit> = vec![];
|
||||
let mut snippets = snippet_edit.into_edit_ranges().into_iter().peekable();
|
||||
let mut text_edits = edit.into_iter();
|
||||
|
||||
while let Some(current_indel) = text_edits.next() {
|
||||
let new_range = {
|
||||
let insert_len =
|
||||
TextSize::try_from(current_indel.insert.len()).unwrap_or(TextSize::from(u32::MAX));
|
||||
TextRange::at(current_indel.delete.start(), insert_len)
|
||||
};
|
||||
|
||||
// insert any snippets before the text edit
|
||||
for (snippet_index, snippet_range) in
|
||||
snippets.take_while_ref(|(_, range)| range.end() < new_range.start())
|
||||
{
|
||||
let snippet_range = if !stdx::always!(
|
||||
snippet_range.is_empty(),
|
||||
"placeholder range {:?} is before current text edit range {:?}",
|
||||
snippet_range,
|
||||
new_range
|
||||
) {
|
||||
// only possible for tabstops, so make sure it's an empty/insert range
|
||||
TextRange::empty(snippet_range.start())
|
||||
} else {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
if snippets.peek().is_some_and(|(_, range)| new_range.intersect(*range).is_some()) {
|
||||
// at least one snippet edit intersects this text edit,
|
||||
// so gather all of the edits that intersect this text edit
|
||||
let mut all_snippets = snippets
|
||||
.take_while_ref(|(_, range)| new_range.intersect(*range).is_some())
|
||||
.collect_vec();
|
||||
|
||||
// ensure all of the ranges are wholly contained inside of the new range
|
||||
all_snippets.retain(|(_, range)| {
|
||||
stdx::always!(
|
||||
new_range.contains_range(*range),
|
||||
"found placeholder range {:?} which wasn't fully inside of text edit's new range {:?}", range, new_range
|
||||
)
|
||||
});
|
||||
|
||||
let mut text_edit = text_edit(line_index, current_indel);
|
||||
|
||||
// escape out snippet text
|
||||
stdx::replace(&mut text_edit.new_text, '\\', r"\\");
|
||||
stdx::replace(&mut text_edit.new_text, '$', r"\$");
|
||||
|
||||
// ...and apply!
|
||||
for (index, range) in all_snippets.iter().rev() {
|
||||
let start = (range.start() - new_range.start()).into();
|
||||
let end = (range.end() - new_range.start()).into();
|
||||
|
||||
if range.is_empty() {
|
||||
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}:"));
|
||||
}
|
||||
}
|
||||
|
||||
edits.push(SnippetTextEdit {
|
||||
range: text_edit.range,
|
||||
new_text: text_edit.new_text,
|
||||
insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
|
||||
annotation_id: None,
|
||||
})
|
||||
} else {
|
||||
// snippet edit was beyond the current one
|
||||
// since it wasn't consumed, it's available for the next pass
|
||||
edits.push(snippet_text_edit(line_index, false, current_indel));
|
||||
}
|
||||
}
|
||||
|
||||
// insert any remaining tabstops
|
||||
edits.extend(snippets.map(|(snippet_index, snippet_range)| {
|
||||
let snippet_range = if !stdx::always!(
|
||||
snippet_range.is_empty(),
|
||||
"found placeholder snippet {:?} without a text edit",
|
||||
snippet_range
|
||||
) {
|
||||
TextRange::empty(snippet_range.start())
|
||||
} else {
|
||||
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,
|
||||
}
|
||||
}));
|
||||
|
||||
edits
|
||||
}
|
||||
|
||||
pub(crate) fn snippet_text_document_edit(
|
||||
snap: &GlobalStateSnapshot,
|
||||
is_snippet: bool,
|
||||
file_id: FileId,
|
||||
edit: TextEdit,
|
||||
snippet_edit: Option<SnippetEdit>,
|
||||
) -> Cancellable<lsp_ext::SnippetTextDocumentEdit> {
|
||||
let text_document = optional_versioned_text_document_identifier(snap, file_id);
|
||||
let line_index = snap.file_line_index(file_id)?;
|
||||
let mut edits: Vec<_> =
|
||||
edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect();
|
||||
let mut edits = if let Some(snippet_edit) = snippet_edit {
|
||||
merge_text_and_snippet_edits(&line_index, edit, snippet_edit)
|
||||
} else {
|
||||
edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect()
|
||||
};
|
||||
|
||||
if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() {
|
||||
for edit in &mut edits {
|
||||
|
@ -974,8 +1094,14 @@ pub(crate) fn snippet_workspace_edit(
|
|||
let ops = snippet_text_document_ops(snap, op)?;
|
||||
document_changes.extend_from_slice(&ops);
|
||||
}
|
||||
for (file_id, edit) in source_change.source_file_edits {
|
||||
let edit = snippet_text_document_edit(snap, source_change.is_snippet, file_id, edit)?;
|
||||
for (file_id, (edit, snippet_edit)) in source_change.source_file_edits {
|
||||
let edit = snippet_text_document_edit(
|
||||
snap,
|
||||
source_change.is_snippet,
|
||||
file_id,
|
||||
edit,
|
||||
snippet_edit,
|
||||
)?;
|
||||
document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
|
||||
}
|
||||
let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit {
|
||||
|
@ -1414,7 +1540,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;
|
||||
|
||||
|
@ -1484,6 +1612,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")]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue