Merge commit 'baee6b338b' into sync-from-ra

This commit is contained in:
Laurențiu Nicola 2023-08-07 12:03:15 +03:00
parent 0155385b57
commit aa55ce9567
139 changed files with 4248 additions and 1042 deletions

View file

@ -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);

View file

@ -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() {

View file

@ -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();

View file

@ -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(

View file

@ -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))

View file

@ -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()

View file

@ -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")]