@@ -39,8 +39,11 @@ pub(crate) const TRIGGER_CHARS: &str = ".=>{";
39
39
// Some features trigger on typing certain characters:
40
40
//
41
41
// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
42
+ // - typing `=` between two expressions adds `;` when in statement position
43
+ // - typing `=` to turn an assignment into an equality comparison removes `;` when in expression position
42
44
// - typing `.` in a chain method call auto-indents
43
45
// - typing `{` in front of an expression inserts a closing `}` after the expression
46
+ // - typing `{` in a use item adds a closing `}` in the right place
44
47
//
45
48
// VS Code::
46
49
//
@@ -166,23 +169,83 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
166
169
if !stdx:: always!( file. syntax( ) . text( ) . char_at( offset) == Some ( '=' ) ) {
167
170
return None ;
168
171
}
169
- let let_stmt: ast:: LetStmt = find_node_at_offset ( file. syntax ( ) , offset) ?;
170
- if let_stmt. semicolon_token ( ) . is_some ( ) {
171
- return None ;
172
+
173
+ if let Some ( edit) = let_stmt ( file, offset) {
174
+ return Some ( edit) ;
175
+ }
176
+ if let Some ( edit) = assign_expr ( file, offset) {
177
+ return Some ( edit) ;
178
+ }
179
+ if let Some ( edit) = assign_to_eq ( file, offset) {
180
+ return Some ( edit) ;
172
181
}
173
- if let Some ( expr) = let_stmt. initializer ( ) {
182
+
183
+ return None ;
184
+
185
+ fn assign_expr ( file : & SourceFile , offset : TextSize ) -> Option < TextEdit > {
186
+ let binop: ast:: BinExpr = find_node_at_offset ( file. syntax ( ) , offset) ?;
187
+ if !matches ! ( binop. op_kind( ) , Some ( ast:: BinaryOp :: Assignment { op: None } ) ) {
188
+ return None ;
189
+ }
190
+
191
+ // Parent must be `ExprStmt` or `StmtList` for `;` to be valid.
192
+ if let Some ( expr_stmt) = ast:: ExprStmt :: cast ( binop. syntax ( ) . parent ( ) ?) {
193
+ if expr_stmt. semicolon_token ( ) . is_some ( ) {
194
+ return None ;
195
+ }
196
+ } else {
197
+ if !ast:: StmtList :: can_cast ( binop. syntax ( ) . parent ( ) ?. kind ( ) ) {
198
+ return None ;
199
+ }
200
+ }
201
+
202
+ let expr = binop. rhs ( ) ?;
174
203
let expr_range = expr. syntax ( ) . text_range ( ) ;
175
204
if expr_range. contains ( offset) && offset != expr_range. start ( ) {
176
205
return None ;
177
206
}
178
207
if file. syntax ( ) . text ( ) . slice ( offset..expr_range. start ( ) ) . contains_char ( '\n' ) {
179
208
return None ;
180
209
}
181
- } else {
182
- return None ;
210
+ let offset = expr. syntax ( ) . text_range ( ) . end ( ) ;
211
+ Some ( TextEdit :: insert ( offset, ";" . to_string ( ) ) )
212
+ }
213
+
214
+ /// `a =$0 b;` removes the semicolon if an expression is valid in this context.
215
+ fn assign_to_eq ( file : & SourceFile , offset : TextSize ) -> Option < TextEdit > {
216
+ let binop: ast:: BinExpr = find_node_at_offset ( file. syntax ( ) , offset) ?;
217
+ if !matches ! ( binop. op_kind( ) , Some ( ast:: BinaryOp :: CmpOp ( ast:: CmpOp :: Eq { negated: false } ) ) )
218
+ {
219
+ return None ;
220
+ }
221
+
222
+ let expr_stmt = ast:: ExprStmt :: cast ( binop. syntax ( ) . parent ( ) ?) ?;
223
+ let semi = expr_stmt. semicolon_token ( ) ?;
224
+
225
+ if expr_stmt. syntax ( ) . next_sibling ( ) . is_some ( ) {
226
+ // Not the last statement in the list.
227
+ return None ;
228
+ }
229
+
230
+ Some ( TextEdit :: delete ( semi. text_range ( ) ) )
231
+ }
232
+
233
+ fn let_stmt ( file : & SourceFile , offset : TextSize ) -> Option < TextEdit > {
234
+ let let_stmt: ast:: LetStmt = find_node_at_offset ( file. syntax ( ) , offset) ?;
235
+ if let_stmt. semicolon_token ( ) . is_some ( ) {
236
+ return None ;
237
+ }
238
+ let expr = let_stmt. initializer ( ) ?;
239
+ let expr_range = expr. syntax ( ) . text_range ( ) ;
240
+ if expr_range. contains ( offset) && offset != expr_range. start ( ) {
241
+ return None ;
242
+ }
243
+ if file. syntax ( ) . text ( ) . slice ( offset..expr_range. start ( ) ) . contains_char ( '\n' ) {
244
+ return None ;
245
+ }
246
+ let offset = let_stmt. syntax ( ) . text_range ( ) . end ( ) ;
247
+ Some ( TextEdit :: insert ( offset, ";" . to_string ( ) ) )
183
248
}
184
- let offset = let_stmt. syntax ( ) . text_range ( ) . end ( ) ;
185
- Some ( TextEdit :: insert ( offset, ";" . to_string ( ) ) )
186
249
}
187
250
188
251
/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
@@ -286,7 +349,7 @@ mod tests {
286
349
}
287
350
288
351
#[ test]
289
- fn test_on_eq_typed ( ) {
352
+ fn test_semi_after_let ( ) {
290
353
// do_check(r"
291
354
// fn foo() {
292
355
// let foo =$0
@@ -322,6 +385,117 @@ fn foo() {
322
385
// ");
323
386
}
324
387
388
+ #[ test]
389
+ fn test_semi_after_assign ( ) {
390
+ type_char (
391
+ '=' ,
392
+ r#"
393
+ fn f() {
394
+ i $0 0
395
+ }
396
+ "# ,
397
+ r#"
398
+ fn f() {
399
+ i = 0;
400
+ }
401
+ "# ,
402
+ ) ;
403
+ type_char (
404
+ '=' ,
405
+ r#"
406
+ fn f() {
407
+ i $0 0
408
+ i
409
+ }
410
+ "# ,
411
+ r#"
412
+ fn f() {
413
+ i = 0;
414
+ i
415
+ }
416
+ "# ,
417
+ ) ;
418
+ type_char_noop (
419
+ '=' ,
420
+ r#"
421
+ fn f(x: u8) {
422
+ if x $0
423
+ }
424
+ "# ,
425
+ ) ;
426
+ type_char_noop (
427
+ '=' ,
428
+ r#"
429
+ fn f(x: u8) {
430
+ if x $0 {}
431
+ }
432
+ "# ,
433
+ ) ;
434
+ type_char_noop (
435
+ '=' ,
436
+ r#"
437
+ fn f(x: u8) {
438
+ if x $0 0 {}
439
+ }
440
+ "# ,
441
+ ) ;
442
+ type_char_noop (
443
+ '=' ,
444
+ r#"
445
+ fn f() {
446
+ g(i $0 0);
447
+ }
448
+ "# ,
449
+ ) ;
450
+ }
451
+
452
+ #[ test]
453
+ fn assign_to_eq ( ) {
454
+ type_char (
455
+ '=' ,
456
+ r#"
457
+ fn f(a: u8) {
458
+ a =$0 0;
459
+ }
460
+ "# ,
461
+ r#"
462
+ fn f(a: u8) {
463
+ a == 0
464
+ }
465
+ "# ,
466
+ ) ;
467
+ type_char (
468
+ '=' ,
469
+ r#"
470
+ fn f(a: u8) {
471
+ a $0= 0;
472
+ }
473
+ "# ,
474
+ r#"
475
+ fn f(a: u8) {
476
+ a == 0
477
+ }
478
+ "# ,
479
+ ) ;
480
+ type_char_noop (
481
+ '=' ,
482
+ r#"
483
+ fn f(a: u8) {
484
+ let e = a =$0 0;
485
+ }
486
+ "# ,
487
+ ) ;
488
+ type_char_noop (
489
+ '=' ,
490
+ r#"
491
+ fn f(a: u8) {
492
+ let e = a =$0 0;
493
+ e
494
+ }
495
+ "# ,
496
+ ) ;
497
+ }
498
+
325
499
#[ test]
326
500
fn indents_new_chain_call ( ) {
327
501
type_char (
0 commit comments