diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs index fcf262877dd..b6e46c32028 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs @@ -410,19 +410,6 @@ impl InlayHint { } } - fn opening_paren_before(kind: InlayKind, range: TextRange) -> InlayHint { - InlayHint { - range, - kind, - label: InlayHintLabel::from("("), - text_edit: None, - position: InlayHintPosition::Before, - pad_left: false, - pad_right: false, - resolve_parent: None, - } - } - pub fn needs_resolve(&self) -> Option { self.resolve_parent.filter(|_| self.text_edit.is_some() || self.label.needs_resolve()) } diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs index ab44d8c3b50..0e0d50b1f35 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs @@ -3,6 +3,8 @@ //! let _: u32 = /* */ loop {}; //! let _: &u32 = /* &* */ &mut 0; //! ``` +use std::ops::Not; + use either::Either; use hir::{ Adjust, Adjustment, AutoBorrow, HirDisplay, Mutability, OverloadedDeref, PointerCast, Safety, @@ -15,6 +17,7 @@ use syntax::{ ast::{self, make, AstNode}, ted, }; +use text_edit::TextEditBuilder; use crate::{ AdjustmentHints, AdjustmentHintsMode, InlayHint, InlayHintLabel, InlayHintLabelPart, @@ -51,13 +54,13 @@ pub(super) fn hints( let adjustments = sema.expr_adjustments(desc_expr).filter(|it| !it.is_empty())?; if let ast::Expr::BlockExpr(_) | ast::Expr::IfExpr(_) | ast::Expr::MatchExpr(_) = desc_expr { - if let [Adjustment { kind: Adjust::Deref(_), source, .. }, Adjustment { kind: Adjust::Borrow(_), source: _, target }] = - &*adjustments - { - // Don't show unnecessary reborrows for these, they will just repeat the inner ones again - if source == target { - return None; - } + // Don't show unnecessary reborrows for these, they will just repeat the inner ones again + if matches!( + &*adjustments, + [Adjustment { kind: Adjust::Deref(_), source, .. }, Adjustment { kind: Adjust::Borrow(_), target, .. }] + if source == target + ) { + return None; } } @@ -101,6 +104,7 @@ pub(super) fn hints( }; let iter: &mut dyn Iterator = iter.as_mut().either(|it| it as _, |it| it as _); + let mut allow_edit = !postfix; for Adjustment { source, target, kind } in iter { if source == target { cov_mark::hit!(same_type_adjustment); @@ -110,6 +114,7 @@ pub(super) fn hints( // FIXME: Add some nicer tooltips to each of these let (text, coercion) = match kind { Adjust::NeverToAny if config.adjustment_hints == AdjustmentHints::Always => { + allow_edit = false; ("", "never to any") } Adjust::Deref(None) => ("*", "dereference"), @@ -130,6 +135,7 @@ pub(super) fn hints( // some of these could be represented via `as` casts, but that's not too nice and // handling everything as a prefix expr makes the `(` and `)` insertion easier Adjust::Pointer(cast) if config.adjustment_hints == AdjustmentHints::Always => { + allow_edit = false; match cast { PointerCast::ReifyFnPointer => { ("", "fn item to fn pointer") @@ -170,12 +176,41 @@ pub(super) fn hints( if needs_outer_parens || (!postfix && needs_inner_parens) { post.label.append_str(")"); } - if !pre.label.parts.is_empty() { - acc.push(pre); + + let mut pre = pre.label.parts.is_empty().not().then_some(pre); + let mut post = post.label.parts.is_empty().not().then_some(post); + if pre.is_none() && post.is_none() { + return None; } - if !post.label.parts.is_empty() { - acc.push(post); + if allow_edit { + let edit = { + let mut b = TextEditBuilder::default(); + if let Some(pre) = &pre { + b.insert( + pre.range.start(), + pre.label.parts.iter().map(|part| &*part.text).collect::(), + ); + } + if let Some(post) = &post { + b.insert( + post.range.end(), + post.label.parts.iter().map(|part| &*part.text).collect::(), + ); + } + b.finish() + }; + match (&mut pre, &mut post) { + (Some(pre), Some(post)) => { + pre.text_edit = Some(edit.clone()); + post.text_edit = Some(edit); + } + (Some(pre), None) => pre.text_edit = Some(edit), + (None, Some(post)) => post.text_edit = Some(edit), + (None, None) => (), + } } + acc.extend(pre); + acc.extend(post); Some(()) } diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs index beba2ad748c..e38450b73f9 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs @@ -9,6 +9,7 @@ use ide_db::famous_defs::FamousDefs; use span::EditionedFileId; use syntax::ast::{self, AstNode}; +use text_edit::TextEditBuilder; use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind}; @@ -23,16 +24,7 @@ pub(super) fn hints( return None; } - let outer_paren_pat = pat - .syntax() - .ancestors() - .skip(1) - .map_while(ast::Pat::cast) - .map_while(|pat| match pat { - ast::Pat::ParenPat(pat) => Some(pat), - _ => None, - }) - .last(); + let outer_paren_pat = pat.syntax().ancestors().skip(1).map_while(ast::ParenPat::cast).last(); let range = outer_paren_pat.as_ref().map_or_else( || match pat { // for ident patterns that @ bind a name, render the un-ref patterns in front of the inner pattern @@ -70,33 +62,30 @@ pub(super) fn hints( hint.label.append_str(r); }); hint.pad_right = was_mut_last; - if !hint.label.parts.is_empty() { - acc.push(hint); - } + let acc_base = acc.len(); match pat { ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => { let bm = sema.binding_mode_of_pat(pat)?; let bm = match bm { - hir::BindingMode::Move => return None, - hir::BindingMode::Ref(Mutability::Mut) => "ref mut", - hir::BindingMode::Ref(Mutability::Shared) => "ref", + hir::BindingMode::Move => None, + hir::BindingMode::Ref(Mutability::Mut) => Some("ref mut"), + hir::BindingMode::Ref(Mutability::Shared) => Some("ref"), }; - acc.push(InlayHint { - range: pat.syntax().text_range(), - kind: InlayKind::BindingMode, - label: bm.into(), - text_edit: None, - position: InlayHintPosition::Before, - pad_left: false, - pad_right: true, - resolve_parent: Some(pat.syntax().text_range()), - }); + if let Some(bm) = bm { + acc.push(InlayHint { + range: pat.syntax().text_range(), + kind: InlayKind::BindingMode, + label: bm.into(), + text_edit: None, + position: InlayHintPosition::Before, + pad_left: false, + pad_right: true, + resolve_parent: Some(pat.syntax().text_range()), + }); + } } ast::Pat::OrPat(pat) if !pattern_adjustments.is_empty() && outer_paren_pat.is_none() => { - acc.push(InlayHint::opening_paren_before( - InlayKind::BindingMode, - pat.syntax().text_range(), - )); + hint.label.append_str("("); acc.push(InlayHint::closing_paren_after( InlayKind::BindingMode, pat.syntax().text_range(), @@ -104,6 +93,24 @@ pub(super) fn hints( } _ => (), } + if !hint.label.parts.is_empty() { + acc.push(hint); + } + + if let hints @ [_, ..] = &mut acc[acc_base..] { + let mut edit = TextEditBuilder::default(); + for h in &mut *hints { + edit.insert( + match h.position { + InlayHintPosition::Before => h.range.start(), + InlayHintPosition::After => h.range.end(), + }, + h.label.parts.iter().map(|p| &*p.text).collect(), + ); + } + let edit = edit.finish(); + hints.iter_mut().for_each(|h| h.text_edit = Some(edit.clone())); + } Some(()) } @@ -154,11 +161,10 @@ fn __( } match &(0,) { (x,) | (x,) => (), - //^^^^^^^^^^^& + //^^^^^^^^^^^) + //^^^^^^^^^^^&( //^ ref //^ ref - //^^^^^^^^^^^( - //^^^^^^^^^^^) ((x,) | (x,)) => (), //^^^^^^^^^^^^^& //^ ref diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs index 35b62878329..40d7367dba3 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs @@ -8,6 +8,7 @@ use hir::Semantics; use ide_db::{famous_defs::FamousDefs, RootDatabase}; use span::EditionedFileId; use syntax::ast::{self, AstNode, HasName}; +use text_edit::TextEdit; use crate::{ DiscriminantHints, InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind, @@ -65,11 +66,11 @@ fn variant_hints( let eq_ = if eq_token.is_none() { " =" } else { "" }; let label = InlayHintLabel::simple( match d { - Ok(x) => { - if x >= 10 { - format!("{eq_} {x} ({x:#X})") + Ok(val) => { + if val >= 10 { + format!("{eq_} {val} ({val:#X})") } else { - format!("{eq_} {x}") + format!("{eq_} {val}") } } Err(_) => format!("{eq_} ?"), @@ -87,7 +88,7 @@ fn variant_hints( }, kind: InlayKind::Discriminant, label, - text_edit: None, + text_edit: d.ok().map(|val| TextEdit::insert(range.start(), format!("{eq_} {val}"))), position: InlayHintPosition::After, pad_left: false, pad_right: false, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs index 8d422478cbf..f15d19047c9 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs @@ -9,6 +9,7 @@ use syntax::{ ast::{self, AstNode}, SyntaxKind, }; +use text_edit::TextEdit; use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, LifetimeElisionHints}; @@ -38,7 +39,7 @@ pub(super) fn hints( range: t.text_range(), kind: InlayKind::Lifetime, label: "'static".into(), - text_edit: None, + text_edit: Some(TextEdit::insert(t.text_range().start(), "'static ".into())), position: InlayHintPosition::After, pad_left: false, pad_right: true,