Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/inlay_hints/closing_brace.rs')
-rw-r--r--crates/ide/src/inlay_hints/closing_brace.rs269
1 files changed, 267 insertions, 2 deletions
diff --git a/crates/ide/src/inlay_hints/closing_brace.rs b/crates/ide/src/inlay_hints/closing_brace.rs
index ab3ce5b05b..49d7d454df 100644
--- a/crates/ide/src/inlay_hints/closing_brace.rs
+++ b/crates/ide/src/inlay_hints/closing_brace.rs
@@ -16,6 +16,8 @@ use crate::{
inlay_hints::LazyProperty,
};
+const ELLIPSIS: &str = "…";
+
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
@@ -60,6 +62,12 @@ pub(super) fn hints(
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 @@ pub(super) fn hints(
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 @@ pub(super) fn hints(
(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 @@ pub(super) fn hints(
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<T>(with: T, arguments: u8, ...) {
}
//^ fn h
+async fn async_fn() {
+ }
+//^ async fn async_fn
+
trait Tr {
fn f();
fn g() {
@@ -260,4 +405,124 @@ fn test() {
"#,
);
}
+
+ #[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",
+ ],
+ ),
+ ]
+ "#]],
+ );
+ }
}