From cde2a1de3616d2e186555572996eecbd37e597e5 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Tue, 12 Apr 2022 19:39:19 +0200 Subject: [PATCH 1/4] Add trailing `;` when typing `=` in assignment --- crates/ide/src/typing.rs | 104 +++++++++++++++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 9 deletions(-) diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 0095012791..62c6ae5c1d 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -166,11 +166,34 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option { if !stdx::always!(file.syntax().text().char_at(offset) == Some('=')) { return None; } - let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; - if let_stmt.semicolon_token().is_some() { - return None; + + if let Some(edit) = let_stmt(file, offset) { + return Some(edit); } - if let Some(expr) = let_stmt.initializer() { + if let Some(edit) = assign_expr(file, offset) { + return Some(edit); + } + + return None; + + fn assign_expr(file: &SourceFile, offset: TextSize) -> Option { + let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?; + if !matches!(binop.op_kind(), Some(ast::BinaryOp::Assignment { op: None })) { + return None; + } + + if let Some(expr_stmt) = ast::ExprStmt::cast(binop.syntax().parent()?) { + if expr_stmt.semicolon_token().is_some() { + return None; + } + } else { + if !ast::StmtList::can_cast(binop.syntax().parent()?.kind()) { + // Parent must be `ExprStmt` or `StmtList` for `;` to be valid. + return None; + } + } + + let expr = binop.rhs()?; let expr_range = expr.syntax().text_range(); if expr_range.contains(offset) && offset != expr_range.start() { return None; @@ -178,11 +201,26 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option { if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') { return None; } - } else { - return None; + let offset = expr.syntax().text_range().end(); + Some(TextEdit::insert(offset, ";".to_string())) + } + + fn let_stmt(file: &SourceFile, offset: TextSize) -> Option { + let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; + if let_stmt.semicolon_token().is_some() { + return None; + } + let expr = let_stmt.initializer()?; + let expr_range = expr.syntax().text_range(); + if expr_range.contains(offset) && offset != expr_range.start() { + return None; + } + if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') { + return None; + } + let offset = let_stmt.syntax().text_range().end(); + Some(TextEdit::insert(offset, ";".to_string())) } - let offset = let_stmt.syntax().text_range().end(); - Some(TextEdit::insert(offset, ";".to_string())) } /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. @@ -286,7 +324,7 @@ mod tests { } #[test] - fn test_on_eq_typed() { + fn test_semi_after_let() { // do_check(r" // fn foo() { // let foo =$0 @@ -322,6 +360,54 @@ fn foo() { // "); } + #[test] + fn test_semi_after_assign() { + type_char( + '=', + r#" +fn f() { + i $0 0 +} +"#, + r#" +fn f() { + i = 0; +} +"#, + ); + type_char( + '=', + r#" +fn f() { + i $0 0 + i +} +"#, + r#" +fn f() { + i = 0; + i +} +"#, + ); + type_char_noop( + '=', + r#" +fn f(x: u8) { + if x $0 +} +"#, + ); + type_char_noop( + '=', + r#" +fn f() { + g(i $0 0); +} +"#, + ); + } + #[test] fn indents_new_chain_call() { type_char( From b9dd7db817164317c3e4d259b8d4526113c42c59 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Wed, 13 Apr 2022 15:47:33 +0200 Subject: [PATCH 2/4] Add more no-op tests --- crates/ide/src/typing.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 62c6ae5c1d..99e7e3287d 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -396,6 +396,22 @@ fn f() { fn f(x: u8) { if x $0 } +"#, + ); + type_char_noop( + '=', + r#" +fn f(x: u8) { + if x $0 {} +} +"#, + ); + type_char_noop( + '=', + r#" +fn f(x: u8) { + if x $0 0 {} +} "#, ); type_char_noop( From 99e9e52fbc9c3b18241e3db874daba6321508018 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Wed, 13 Apr 2022 16:01:09 +0200 Subject: [PATCH 3/4] Remove trailing `;` when turning assignment into `==` comparison --- crates/ide/src/typing.rs | 71 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 99e7e3287d..24d4073170 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -173,6 +173,9 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option { if let Some(edit) = assign_expr(file, offset) { return Some(edit); } + if let Some(edit) = assign_to_eq(file, offset) { + return Some(edit); + } return None; @@ -182,13 +185,13 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option { return None; } + // Parent must be `ExprStmt` or `StmtList` for `;` to be valid. if let Some(expr_stmt) = ast::ExprStmt::cast(binop.syntax().parent()?) { if expr_stmt.semicolon_token().is_some() { return None; } } else { if !ast::StmtList::can_cast(binop.syntax().parent()?.kind()) { - // Parent must be `ExprStmt` or `StmtList` for `;` to be valid. return None; } } @@ -205,6 +208,25 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option { Some(TextEdit::insert(offset, ";".to_string())) } + /// `a =$0 b;` removes the semicolon if an expression is valid in this context. + fn assign_to_eq(file: &SourceFile, offset: TextSize) -> Option { + let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?; + if !matches!(binop.op_kind(), Some(ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false }))) + { + return None; + } + + let expr_stmt = ast::ExprStmt::cast(binop.syntax().parent()?)?; + let semi = expr_stmt.semicolon_token()?; + + if expr_stmt.syntax().next_sibling().is_some() { + // Not the last statement in the list. + return None; + } + + Some(TextEdit::delete(semi.text_range())) + } + fn let_stmt(file: &SourceFile, offset: TextSize) -> Option { let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; if let_stmt.semicolon_token().is_some() { @@ -424,6 +446,53 @@ fn f() { ); } + #[test] + fn assign_to_eq() { + type_char( + '=', + r#" +fn f(a: u8) { + a =$0 0; +} +"#, + r#" +fn f(a: u8) { + a == 0 +} +"#, + ); + type_char( + '=', + r#" +fn f(a: u8) { + a $0= 0; +} +"#, + r#" +fn f(a: u8) { + a == 0 +} +"#, + ); + type_char_noop( + '=', + r#" +fn f(a: u8) { + let e = a =$0 0; +} +"#, + ); + type_char_noop( + '=', + r#" +fn f(a: u8) { + let e = a =$0 0; + e +} +"#, + ); + } + #[test] fn indents_new_chain_call() { type_char( From f96fd401049f11842b8e934092024566fcabbb8e Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Thu, 14 Apr 2022 13:23:19 +0200 Subject: [PATCH 4/4] add docs --- crates/ide/src/typing.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 24d4073170..a75e6be8b8 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -39,8 +39,11 @@ pub(crate) const TRIGGER_CHARS: &str = ".=>{"; // Some features trigger on typing certain characters: // // - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression +// - typing `=` between two expressions adds `;` when in statement position +// - typing `=` to turn an assignment into an equality comparison removes `;` when in expression position // - typing `.` in a chain method call auto-indents // - typing `{` in front of an expression inserts a closing `}` after the expression +// - typing `{` in a use item adds a closing `}` in the right place // // VS Code:: //