Merge pull request #21147 from Wilfred/imports_granularity_dropdown
fix: rust-analyzer.imports.granularity.group should get a dropdown UI
diff --git a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
index 61d8449..f8b9bb6 100644
--- a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
+++ b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -1197,4 +1197,57 @@
"#,
);
}
+
+ #[test]
+ fn regression_issue_21020() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+pub struct S$0(pub ());
+
+trait T {
+ fn id(&self) -> usize;
+}
+
+trait T2 {
+ fn foo(&self) -> usize;
+}
+
+impl T for S {
+ fn id(&self) -> usize {
+ self.0.len()
+ }
+}
+
+impl T2 for S {
+ fn foo(&self) -> usize {
+ self.0.len()
+ }
+}
+ "#,
+ r#"
+pub struct S { pub field1: () }
+
+trait T {
+ fn id(&self) -> usize;
+}
+
+trait T2 {
+ fn foo(&self) -> usize;
+}
+
+impl T for S {
+ fn id(&self) -> usize {
+ self.field1.len()
+ }
+}
+
+impl T2 for S {
+ fn foo(&self) -> usize {
+ self.field1.len()
+ }
+}
+ "#,
+ );
+ }
}
diff --git a/crates/ide/src/inlay_hints/closing_brace.rs b/crates/ide/src/inlay_hints/closing_brace.rs
index ab3ce5b..49d7d45 100644
--- a/crates/ide/src/inlay_hints/closing_brace.rs
+++ b/crates/ide/src/inlay_hints/closing_brace.rs
@@ -16,6 +16,8 @@
inlay_hints::LazyProperty,
};
+const ELLIPSIS: &str = "…";
+
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
@@ -60,6 +62,12 @@
let module = ast::Module::cast(list.syntax().parent()?)?;
(format!("mod {}", module.name()?), module.name().map(name))
+ } else if let Some(match_arm_list) = ast::MatchArmList::cast(node.clone()) {
+ closing_token = match_arm_list.r_curly_token()?;
+
+ let match_expr = ast::MatchExpr::cast(match_arm_list.syntax().parent()?)?;
+ let label = format_match_label(&match_expr, config)?;
+ (label, None)
} else if let Some(label) = ast::Label::cast(node.clone()) {
// in this case, `ast::Label` could be seen as a part of `ast::BlockExpr`
// the actual number of lines in this case should be the line count of the parent BlockExpr,
@@ -91,7 +99,7 @@
match_ast! {
match parent {
ast::Fn(it) => {
- (format!("fn {}", it.name()?), it.name().map(name))
+ (format!("{}fn {}", fn_qualifiers(&it), it.name()?), it.name().map(name))
},
ast::Static(it) => (format!("static {}", it.name()?), it.name().map(name)),
ast::Const(it) => {
@@ -101,6 +109,33 @@
(format!("const {}", it.name()?), it.name().map(name))
}
},
+ ast::LoopExpr(loop_expr) => {
+ if loop_expr.label().is_some() {
+ return None;
+ }
+ ("loop".into(), None)
+ },
+ ast::WhileExpr(while_expr) => {
+ if while_expr.label().is_some() {
+ return None;
+ }
+ (keyword_with_condition("while", while_expr.condition(), config), None)
+ },
+ ast::ForExpr(for_expr) => {
+ if for_expr.label().is_some() {
+ return None;
+ }
+ let label = format_for_label(&for_expr, config)?;
+ (label, None)
+ },
+ ast::IfExpr(if_expr) => {
+ let label = label_for_if_block(&if_expr, &block, config)?;
+ (label, None)
+ },
+ ast::LetElse(let_else) => {
+ let label = format_let_else_label(&let_else, config)?;
+ (label, None)
+ },
_ => return None,
}
}
@@ -154,11 +189,117 @@
None
}
+fn fn_qualifiers(func: &ast::Fn) -> String {
+ let mut qualifiers = String::new();
+ if func.const_token().is_some() {
+ qualifiers.push_str("const ");
+ }
+ if func.async_token().is_some() {
+ qualifiers.push_str("async ");
+ }
+ if func.unsafe_token().is_some() {
+ qualifiers.push_str("unsafe ");
+ }
+ qualifiers
+}
+
+fn keyword_with_condition(
+ keyword: &str,
+ condition: Option<ast::Expr>,
+ config: &InlayHintsConfig<'_>,
+) -> String {
+ if let Some(expr) = condition {
+ return format!("{keyword} {}", snippet_from_node(expr.syntax(), config));
+ }
+ keyword.to_owned()
+}
+
+fn format_for_label(for_expr: &ast::ForExpr, config: &InlayHintsConfig<'_>) -> Option<String> {
+ let pat = for_expr.pat()?;
+ let iterable = for_expr.iterable()?;
+ Some(format!(
+ "for {} in {}",
+ snippet_from_node(pat.syntax(), config),
+ snippet_from_node(iterable.syntax(), config)
+ ))
+}
+
+fn format_match_label(
+ match_expr: &ast::MatchExpr,
+ config: &InlayHintsConfig<'_>,
+) -> Option<String> {
+ let expr = match_expr.expr()?;
+ Some(format!("match {}", snippet_from_node(expr.syntax(), config)))
+}
+
+fn label_for_if_block(
+ if_expr: &ast::IfExpr,
+ block: &ast::BlockExpr,
+ config: &InlayHintsConfig<'_>,
+) -> Option<String> {
+ if if_expr.then_branch().is_some_and(|then_branch| then_branch.syntax() == block.syntax()) {
+ Some(keyword_with_condition("if", if_expr.condition(), config))
+ } else if matches!(
+ if_expr.else_branch(),
+ Some(ast::ElseBranch::Block(else_block)) if else_block.syntax() == block.syntax()
+ ) {
+ Some("else".into())
+ } else {
+ None
+ }
+}
+
+fn format_let_else_label(let_else: &ast::LetElse, config: &InlayHintsConfig<'_>) -> Option<String> {
+ let stmt = let_else.syntax().parent().and_then(ast::LetStmt::cast)?;
+ let pat = stmt.pat()?;
+ let initializer = stmt.initializer()?;
+ Some(format!(
+ "let {} = {} else",
+ snippet_from_node(pat.syntax(), config),
+ snippet_from_node(initializer.syntax(), config)
+ ))
+}
+
+fn snippet_from_node(node: &SyntaxNode, config: &InlayHintsConfig<'_>) -> String {
+ let mut text = node.text().to_string();
+ if text.contains('\n') {
+ return ELLIPSIS.into();
+ }
+
+ let Some(limit) = config.max_length else {
+ return text;
+ };
+ if limit == 0 {
+ return ELLIPSIS.into();
+ }
+
+ if text.len() <= limit {
+ return text;
+ }
+
+ let boundary = text.floor_char_boundary(limit.min(text.len()));
+ if boundary == text.len() {
+ return text;
+ }
+
+ let cut = text[..boundary]
+ .char_indices()
+ .rev()
+ .find(|&(_, ch)| ch == ' ')
+ .map(|(idx, _)| idx)
+ .unwrap_or(0);
+ text.truncate(cut);
+ text.push_str(ELLIPSIS);
+ text
+}
+
#[cfg(test)]
mod tests {
+ use expect_test::expect;
+
use crate::{
InlayHintsConfig,
- inlay_hints::tests::{DISABLED_CONFIG, check_with_config},
+ inlay_hints::tests::{DISABLED_CONFIG, check_expect, check_with_config},
};
#[test]
@@ -179,6 +320,10 @@
}
//^ fn h
+async fn async_fn() {
+ }
+//^ async fn async_fn
+
trait Tr {
fn f();
fn g() {
@@ -260,4 +405,124 @@
"#,
);
}
+
+ #[test]
+ fn hints_closing_brace_additional_blocks() {
+ check_expect(
+ InlayHintsConfig { closing_brace_hints_min_lines: Some(2), ..DISABLED_CONFIG },
+ r#"
+fn demo() {
+ loop {
+
+ }
+
+ while let Some(value) = next() {
+
+ }
+
+ for value in iter {
+
+ }
+
+ if cond {
+
+ }
+
+ if let Some(x) = maybe {
+
+ }
+
+ if other {
+ } else {
+
+ }
+
+ let Some(v) = maybe else {
+
+ };
+
+ match maybe {
+ Some(v) => {
+
+ }
+ value if check(value) => {
+
+ }
+ None => {}
+ }
+}
+"#,
+ expect![[r#"
+ [
+ (
+ 364..365,
+ [
+ InlayHintLabelPart {
+ text: "fn demo",
+ linked_location: Some(
+ Computed(
+ FileRangeWrapper {
+ file_id: FileId(
+ 0,
+ ),
+ range: 3..7,
+ },
+ ),
+ ),
+ tooltip: "",
+ },
+ ],
+ ),
+ (
+ 28..29,
+ [
+ "loop",
+ ],
+ ),
+ (
+ 73..74,
+ [
+ "while let Some(value) = next()",
+ ],
+ ),
+ (
+ 105..106,
+ [
+ "for value in iter",
+ ],
+ ),
+ (
+ 127..128,
+ [
+ "if cond",
+ ],
+ ),
+ (
+ 164..165,
+ [
+ "if let Some(x) = maybe",
+ ],
+ ),
+ (
+ 200..201,
+ [
+ "else",
+ ],
+ ),
+ (
+ 240..241,
+ [
+ "let Some(v) = maybe else",
+ ],
+ ),
+ (
+ 362..363,
+ [
+ "match maybe",
+ ],
+ ),
+ ]
+ "#]],
+ );
+ }
}
diff --git a/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs b/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs
index d22e3f1..425bd9b 100644
--- a/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs
+++ b/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs
@@ -216,16 +216,7 @@
text: Vec::new(),
version,
};
- let group = proc_macro_srv::Group {
- delimiter: proc_macro_srv::Delimiter::None,
- stream: Some(tokenstream),
- span: proc_macro_srv::DelimSpan {
- open: call_site,
- close: call_site,
- entire: call_site,
- },
- };
- w.write_tokenstream(&group);
+ w.write_tokenstream(call_site, &tokenstream);
FlatTree {
subtree: if version >= ENCODE_CLOSE_SPAN_VERSION {
@@ -267,16 +258,7 @@
text: Vec::new(),
version,
};
- let group = proc_macro_srv::Group {
- delimiter: proc_macro_srv::Delimiter::None,
- stream: Some(tokenstream),
- span: proc_macro_srv::DelimSpan {
- open: call_site,
- close: call_site,
- entire: call_site,
- },
- };
- w.write_tokenstream(&group);
+ w.write_tokenstream(call_site, &tokenstream);
FlatTree {
subtree: if version >= ENCODE_CLOSE_SPAN_VERSION {
@@ -491,7 +473,7 @@
}
struct Writer<'a, 'span, S: SpanTransformer, W> {
- work: VecDeque<(usize, W)>,
+ work: VecDeque<(usize, usize, W)>,
string_table: FxHashMap<std::borrow::Cow<'a, str>, u32>,
span_data_table: &'span mut S::Table,
version: u32,
@@ -508,14 +490,13 @@
fn write_subtree(&mut self, root: tt::SubtreeView<'a, T::Span>) {
let subtree = root.top_subtree();
self.enqueue(subtree, root.iter());
- while let Some((idx, subtree)) = self.work.pop_front() {
- self.subtree(idx, subtree);
+ while let Some((idx, len, subtree)) = self.work.pop_front() {
+ self.subtree(idx, len, subtree);
}
}
- fn subtree(&mut self, idx: usize, subtree: tt::iter::TtIter<'a, T::Span>) {
+ fn subtree(&mut self, idx: usize, n_tt: usize, subtree: tt::iter::TtIter<'a, T::Span>) {
let mut first_tt = self.token_tree.len();
- let n_tt = subtree.clone().count(); // FIXME: `count()` walks over the entire iterator.
self.token_tree.resize(first_tt + n_tt, !0);
self.subtree[idx].tt = [first_tt as u32, (first_tt + n_tt) as u32];
@@ -594,7 +575,8 @@
let close = self.token_id_of(subtree.delimiter.close);
let delimiter_kind = subtree.delimiter.kind;
self.subtree.push(SubtreeRepr { open, close, kind: delimiter_kind, tt: [!0, !0] });
- self.work.push_back((idx, contents));
+ // FIXME: `count()` walks over the entire iterator.
+ self.work.push_back((idx, contents.clone().count(), contents));
idx as u32
}
}
@@ -624,26 +606,43 @@
}
#[cfg(feature = "sysroot-abi")]
-impl<'a, T: SpanTransformer> Writer<'a, '_, T, &'a proc_macro_srv::Group<T::Span>> {
- fn write_tokenstream(&mut self, root: &'a proc_macro_srv::Group<T::Span>) {
- self.enqueue_group(root);
+impl<'a, T: SpanTransformer>
+ Writer<'a, '_, T, Option<proc_macro_srv::TokenStreamIter<'a, T::Span>>>
+{
+ fn write_tokenstream(
+ &mut self,
+ call_site: T::Span,
+ root: &'a proc_macro_srv::TokenStream<T::Span>,
+ ) {
+ let call_site = self.token_id_of(call_site);
+ self.subtree.push(SubtreeRepr {
+ open: call_site,
+ close: call_site,
+ kind: tt::DelimiterKind::Invisible,
+ tt: [!0, !0],
+ });
+ self.work.push_back((0, root.len(), Some(root.iter())));
- while let Some((idx, group)) = self.work.pop_front() {
- self.group(idx, group);
+ while let Some((idx, len, group)) = self.work.pop_front() {
+ self.group(idx, len, group);
}
}
- fn group(&mut self, idx: usize, group: &'a proc_macro_srv::Group<T::Span>) {
+ fn group(
+ &mut self,
+ idx: usize,
+ n_tt: usize,
+ group: Option<proc_macro_srv::TokenStreamIter<'a, T::Span>>,
+ ) {
let mut first_tt = self.token_tree.len();
- let n_tt = group.stream.as_ref().map_or(0, |it| it.len());
self.token_tree.resize(first_tt + n_tt, !0);
self.subtree[idx].tt = [first_tt as u32, (first_tt + n_tt) as u32];
- for tt in group.stream.iter().flat_map(|it| it.iter()) {
+ for tt in group.into_iter().flatten() {
let idx_tag = match tt {
proc_macro_srv::TokenTree::Group(group) => {
- let idx = self.enqueue_group(group);
+ let idx = self.enqueue(group);
idx << 2
}
proc_macro_srv::TokenTree::Literal(lit) => {
@@ -706,7 +705,7 @@
}
}
- fn enqueue_group(&mut self, group: &'a proc_macro_srv::Group<T::Span>) -> u32 {
+ fn enqueue(&mut self, group: &'a proc_macro_srv::Group<T::Span>) -> u32 {
let idx = self.subtree.len();
let open = self.token_id_of(group.span.open);
let close = self.token_id_of(group.span.close);
@@ -717,7 +716,11 @@
proc_macro_srv::Delimiter::None => tt::DelimiterKind::Invisible,
};
self.subtree.push(SubtreeRepr { open, close, kind: delimiter_kind, tt: [!0, !0] });
- self.work.push_back((idx, group));
+ self.work.push_back((
+ idx,
+ group.stream.as_ref().map_or(0, |stream| stream.len()),
+ group.stream.as_ref().map(|ts| ts.iter()),
+ ));
idx as u32
}
}
@@ -959,7 +962,6 @@
};
res[i] = Some(g);
}
- // FIXME: double check this
proc_macro_srv::TokenStream::new(vec![proc_macro_srv::TokenTree::Group(
res[0].take().unwrap(),
)])
diff --git a/crates/proc-macro-srv/src/lib.rs b/crates/proc-macro-srv/src/lib.rs
index a96cf2b..0ac58d7 100644
--- a/crates/proc-macro-srv/src/lib.rs
+++ b/crates/proc-macro-srv/src/lib.rs
@@ -56,7 +56,7 @@
pub use crate::bridge::*;
pub use crate::server_impl::literal_from_str;
-pub use crate::token_stream::{TokenStream, literal_to_string};
+pub use crate::token_stream::{TokenStream, TokenStreamIter, literal_to_string};
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ProcMacroKind {
diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs
index 0b35887..5683d89 100644
--- a/crates/syntax/src/syntax_editor.rs
+++ b/crates/syntax/src/syntax_editor.rs
@@ -653,4 +653,58 @@
let expect = expect![["fn it() {\n \n}"]];
expect.assert_eq(&edit.new_root.to_string());
}
+
+ #[test]
+ fn test_more_times_replace_node_to_mutable_token() {
+ let arg_list =
+ make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]);
+
+ let mut editor = SyntaxEditor::new(arg_list.syntax().clone());
+ let target_expr = make::token(parser::SyntaxKind::UNDERSCORE);
+
+ for arg in arg_list.args() {
+ editor.replace(arg.syntax(), &target_expr);
+ }
+
+ let edit = editor.finish();
+
+ let expect = expect![["(_, _)"]];
+ expect.assert_eq(&edit.new_root.to_string());
+ }
+
+ #[test]
+ fn test_more_times_replace_node_to_mutable() {
+ let arg_list =
+ make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]);
+
+ let mut editor = SyntaxEditor::new(arg_list.syntax().clone());
+ let target_expr = make::expr_literal("3").clone_for_update();
+
+ for arg in arg_list.args() {
+ editor.replace(arg.syntax(), target_expr.syntax());
+ }
+
+ let edit = editor.finish();
+
+ let expect = expect![["(3, 3)"]];
+ expect.assert_eq(&edit.new_root.to_string());
+ }
+
+ #[test]
+ fn test_more_times_insert_node_to_mutable() {
+ let arg_list =
+ make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]);
+
+ let mut editor = SyntaxEditor::new(arg_list.syntax().clone());
+ let target_expr = make::ext::expr_unit().clone_for_update();
+
+ for arg in arg_list.args() {
+ editor.insert(Position::before(arg.syntax()), target_expr.syntax());
+ }
+
+ let edit = editor.finish();
+
+ let expect = expect![["(()1, ()2)"]];
+ expect.assert_eq(&edit.new_root.to_string());
+ }
}
diff --git a/crates/syntax/src/syntax_editor/edit_algo.rs b/crates/syntax/src/syntax_editor/edit_algo.rs
index 01c1f0d..e697d97 100644
--- a/crates/syntax/src/syntax_editor/edit_algo.rs
+++ b/crates/syntax/src/syntax_editor/edit_algo.rs
@@ -150,6 +150,35 @@
// Map change targets to the correct syntax nodes
let tree_mutator = TreeMutator::new(&root);
let mut changed_elements = vec![];
+ let mut changed_elements_set = rustc_hash::FxHashSet::default();
+ let mut deduplicate_node = |node_or_token: &mut SyntaxElement| {
+ let node;
+ let node = match node_or_token {
+ SyntaxElement::Token(token) => match token.parent() {
+ None => return,
+ Some(parent) => {
+ node = parent;
+ &node
+ }
+ },
+ SyntaxElement::Node(node) => node,
+ };
+ if changed_elements_set.contains(node) {
+ let new_node = node.clone_subtree().clone_for_update();
+ match node_or_token {
+ SyntaxElement::Node(node) => *node = new_node,
+ SyntaxElement::Token(token) => {
+ *token = new_node
+ .children_with_tokens()
+ .filter_map(SyntaxElement::into_token)
+ .find(|it| it.kind() == token.kind() && it.text() == token.text())
+ .unwrap();
+ }
+ }
+ } else {
+ changed_elements_set.insert(node.clone());
+ }
+ };
for index in independent_changes {
match &mut changes[index as usize] {
@@ -180,6 +209,18 @@
}
}
+ match &mut changes[index as usize] {
+ Change::Insert(_, element) | Change::Replace(_, Some(element)) => {
+ deduplicate_node(element);
+ }
+ Change::InsertAll(_, elements)
+ | Change::ReplaceWithMany(_, elements)
+ | Change::ReplaceAll(_, elements) => {
+ elements.iter_mut().for_each(&mut deduplicate_node);
+ }
+ Change::Replace(_, None) => (),
+ }
+
// Collect changed elements
match &changes[index as usize] {
Change::Insert(_, element) => changed_elements.push(element.clone()),