aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/join_lines.rs69
-rw-r--r--crates/ide/src/references.rs64
-rw-r--r--crates/ide/src/references/rename.rs17
-rw-r--r--crates/ide/src/typing.rs150
-rw-r--r--crates/ide/src/typing/on_enter.rs115
5 files changed, 377 insertions, 38 deletions
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index fe2a349e6..61dcbb399 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -1,3 +1,5 @@
1use std::convert::TryFrom;
2
1use ide_assists::utils::extract_trivial_expression; 3use ide_assists::utils::extract_trivial_expression;
2use itertools::Itertools; 4use itertools::Itertools;
3use syntax::{ 5use syntax::{
@@ -65,14 +67,6 @@ fn remove_newlines(edit: &mut TextEditBuilder, token: &SyntaxToken, range: TextR
65 67
66fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) { 68fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) {
67 if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 { 69 if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 {
68 let mut string_open_quote = false;
69 if let Some(string) = ast::String::cast(token.clone()) {
70 if let Some(range) = string.open_quote_text_range() {
71 cov_mark::hit!(join_string_literal);
72 string_open_quote = range.end() == offset;
73 }
74 }
75
76 let n_spaces_after_line_break = { 70 let n_spaces_after_line_break = {
77 let suff = &token.text()[TextRange::new( 71 let suff = &token.text()[TextRange::new(
78 offset - token.text_range().start() + TextSize::of('\n'), 72 offset - token.text_range().start() + TextSize::of('\n'),
@@ -81,8 +75,23 @@ fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextS
81 suff.bytes().take_while(|&b| b == b' ').count() 75 suff.bytes().take_while(|&b| b == b' ').count()
82 }; 76 };
83 77
78 let mut no_space = false;
79 if let Some(string) = ast::String::cast(token.clone()) {
80 if let Some(range) = string.open_quote_text_range() {
81 cov_mark::hit!(join_string_literal_open_quote);
82 no_space |= range.end() == offset;
83 }
84 if let Some(range) = string.close_quote_text_range() {
85 cov_mark::hit!(join_string_literal_close_quote);
86 no_space |= range.start()
87 == offset
88 + TextSize::of('\n')
89 + TextSize::try_from(n_spaces_after_line_break).unwrap();
90 }
91 }
92
84 let range = TextRange::at(offset, ((n_spaces_after_line_break + 1) as u32).into()); 93 let range = TextRange::at(offset, ((n_spaces_after_line_break + 1) as u32).into());
85 let replace_with = if string_open_quote { "" } else { " " }; 94 let replace_with = if no_space { "" } else { " " };
86 edit.replace(range, replace_with.to_string()); 95 edit.replace(range, replace_with.to_string());
87 return; 96 return;
88 } 97 }
@@ -797,22 +806,54 @@ fn foo() {
797 806
798 #[test] 807 #[test]
799 fn join_string_literal() { 808 fn join_string_literal() {
800 cov_mark::check!(join_string_literal); 809 {
801 check_join_lines( 810 cov_mark::check!(join_string_literal_open_quote);
802 r#" 811 check_join_lines(
812 r#"
803fn main() { 813fn main() {
804 $0" 814 $0"
805hello 815hello
806"; 816";
807} 817}
808"#, 818"#,
809 r#" 819 r#"
810fn main() { 820fn main() {
811 $0"hello 821 $0"hello
812"; 822";
813} 823}
814"#, 824"#,
815 ); 825 );
826 }
827
828 {
829 cov_mark::check!(join_string_literal_close_quote);
830 check_join_lines(
831 r#"
832fn main() {
833 $0"hello
834";
835}
836"#,
837 r#"
838fn main() {
839 $0"hello";
840}
841"#,
842 );
843 check_join_lines(
844 r#"
845fn main() {
846 $0r"hello
847 ";
848}
849"#,
850 r#"
851fn main() {
852 $0r"hello";
853}
854"#,
855 );
856 }
816 857
817 check_join_lines( 858 check_join_lines(
818 r#" 859 r#"
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 11ca7ec6b..ae492a264 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -65,7 +65,7 @@ pub(crate) fn find_all_refs(
65 (find_def(&sema, &syntax, position)?, false) 65 (find_def(&sema, &syntax, position)?, false)
66 }; 66 };
67 67
68 let mut usages = def.usages(sema).set_scope(search_scope).all(); 68 let mut usages = def.usages(sema).set_scope(search_scope).include_self_refs().all();
69 if is_literal_search { 69 if is_literal_search {
70 // filter for constructor-literals 70 // filter for constructor-literals
71 let refs = usages.references.values_mut(); 71 let refs = usages.references.values_mut();
@@ -1163,22 +1163,76 @@ fn foo<const FOO$0: usize>() -> usize {
1163 } 1163 }
1164 1164
1165 #[test] 1165 #[test]
1166 fn test_find_self_ty_in_trait_def() { 1166 fn test_trait() {
1167 check( 1167 check(
1168 r#" 1168 r#"
1169trait Foo { 1169trait Foo$0 where Self: {}
1170 fn f() -> Self$0; 1170
1171impl Foo for () {}
1172"#,
1173 expect![[r#"
1174 Foo Trait FileId(0) 0..24 6..9
1175
1176 FileId(0) 31..34
1177 "#]],
1178 );
1179 }
1180
1181 #[test]
1182 fn test_trait_self() {
1183 check(
1184 r#"
1185trait Foo where Self$0 {
1186 fn f() -> Self;
1171} 1187}
1188
1189impl Foo for () {}
1172"#, 1190"#,
1173 expect![[r#" 1191 expect![[r#"
1174 Self TypeParam FileId(0) 6..9 6..9 1192 Self TypeParam FileId(0) 6..9 6..9
1175 1193
1176 FileId(0) 26..30 1194 FileId(0) 16..20
1195 FileId(0) 37..41
1177 "#]], 1196 "#]],
1178 ); 1197 );
1179 } 1198 }
1180 1199
1181 #[test] 1200 #[test]
1201 fn test_self_ty() {
1202 check(
1203 r#"
1204 struct $0Foo;
1205
1206 impl Foo where Self: {
1207 fn f() -> Self;
1208 }
1209 "#,
1210 expect![[r#"
1211 Foo Struct FileId(0) 0..11 7..10
1212
1213 FileId(0) 18..21
1214 FileId(0) 28..32
1215 FileId(0) 50..54
1216 "#]],
1217 );
1218 check(
1219 r#"
1220struct Foo;
1221
1222impl Foo where Self: {
1223 fn f() -> Self$0;
1224}
1225"#,
1226 expect![[r#"
1227 impl Impl FileId(0) 13..57 18..21
1228
1229 FileId(0) 18..21
1230 FileId(0) 28..32
1231 FileId(0) 50..54
1232 "#]],
1233 );
1234 }
1235 #[test]
1182 fn test_self_variant_with_payload() { 1236 fn test_self_variant_with_payload() {
1183 check( 1237 check(
1184 r#" 1238 r#"
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 175e7a31d..2bf953305 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1888,4 +1888,21 @@ impl Foo {
1888 "error: Cannot rename `Self`", 1888 "error: Cannot rename `Self`",
1889 ); 1889 );
1890 } 1890 }
1891
1892 #[test]
1893 fn test_rename_ignores_self_ty() {
1894 check(
1895 "Fo0",
1896 r#"
1897struct $0Foo;
1898
1899impl Foo where Self: {}
1900"#,
1901 r#"
1902struct Fo0;
1903
1904impl Fo0 where Self: {}
1905"#,
1906 );
1907 }
1891} 1908}
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index 82c732390..4ad49eca0 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -88,7 +88,7 @@ fn on_char_typed_inner(
88} 88}
89 89
90/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a 90/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
91/// block. 91/// block, or a part of a `use` item.
92fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> { 92fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> {
93 if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) { 93 if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) {
94 return None; 94 return None;
@@ -99,30 +99,59 @@ fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<
99 // Remove the `{` to get a better parse tree, and reparse 99 // Remove the `{` to get a better parse tree, and reparse
100 let file = file.reparse(&Indel::delete(brace_token.text_range())); 100 let file = file.reparse(&Indel::delete(brace_token.text_range()));
101 101
102 let mut expr: ast::Expr = find_node_at_offset(file.tree().syntax(), offset)?; 102 if let Some(edit) = brace_expr(&file.tree(), offset) {
103 if expr.syntax().text_range().start() != offset { 103 return Some(edit);
104 return None;
105 } 104 }
106 105
107 // Enclose the outermost expression starting at `offset` 106 if let Some(edit) = brace_use_path(&file.tree(), offset) {
108 while let Some(parent) = expr.syntax().parent() { 107 return Some(edit);
109 if parent.text_range().start() != expr.syntax().text_range().start() { 108 }
110 break;
111 }
112 109
113 match ast::Expr::cast(parent) { 110 return None;
114 Some(parent) => expr = parent, 111
115 None => break, 112 fn brace_use_path(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
113 let segment: ast::PathSegment = find_node_at_offset(file.syntax(), offset)?;
114 if segment.syntax().text_range().start() != offset {
115 return None;
116 } 116 }
117 }
118 117
119 // If it's a statement in a block, we don't know how many statements should be included 118 let tree: ast::UseTree = find_node_at_offset(file.syntax(), offset)?;
120 if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) { 119
121 return None; 120 Some(TextEdit::insert(
121 tree.syntax().text_range().end() + TextSize::of("{"),
122 "}".to_string(),
123 ))
122 } 124 }
123 125
124 // Insert `}` right after the expression. 126 fn brace_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
125 Some(TextEdit::insert(expr.syntax().text_range().end() + TextSize::of("{"), "}".to_string())) 127 let mut expr: ast::Expr = find_node_at_offset(file.syntax(), offset)?;
128 if expr.syntax().text_range().start() != offset {
129 return None;
130 }
131
132 // Enclose the outermost expression starting at `offset`
133 while let Some(parent) = expr.syntax().parent() {
134 if parent.text_range().start() != expr.syntax().text_range().start() {
135 break;
136 }
137
138 match ast::Expr::cast(parent) {
139 Some(parent) => expr = parent,
140 None => break,
141 }
142 }
143
144 // If it's a statement in a block, we don't know how many statements should be included
145 if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) {
146 return None;
147 }
148
149 // Insert `}` right after the expression.
150 Some(TextEdit::insert(
151 expr.syntax().text_range().end() + TextSize::of("{"),
152 "}".to_string(),
153 ))
154 }
126} 155}
127 156
128/// Returns an edit which should be applied after `=` was typed. Primarily, 157/// Returns an edit which should be applied after `=` was typed. Primarily,
@@ -440,7 +469,7 @@ fn foo() -> { 92 }
440 } 469 }
441 470
442 #[test] 471 #[test]
443 fn adds_closing_brace() { 472 fn adds_closing_brace_for_expr() {
444 type_char( 473 type_char(
445 '{', 474 '{',
446 r#" 475 r#"
@@ -519,4 +548,87 @@ fn f() {
519 "#, 548 "#,
520 ); 549 );
521 } 550 }
551
552 #[test]
553 fn adds_closing_brace_for_use_tree() {
554 type_char(
555 '{',
556 r#"
557use some::$0Path;
558 "#,
559 r#"
560use some::{Path};
561 "#,
562 );
563 type_char(
564 '{',
565 r#"
566use some::{Path, $0Other};
567 "#,
568 r#"
569use some::{Path, {Other}};
570 "#,
571 );
572 type_char(
573 '{',
574 r#"
575use some::{$0Path, Other};
576 "#,
577 r#"
578use some::{{Path}, Other};
579 "#,
580 );
581 type_char(
582 '{',
583 r#"
584use some::path::$0to::Item;
585 "#,
586 r#"
587use some::path::{to::Item};
588 "#,
589 );
590 type_char(
591 '{',
592 r#"
593use some::$0path::to::Item;
594 "#,
595 r#"
596use some::{path::to::Item};
597 "#,
598 );
599 type_char(
600 '{',
601 r#"
602use $0some::path::to::Item;
603 "#,
604 r#"
605use {some::path::to::Item};
606 "#,
607 );
608 type_char(
609 '{',
610 r#"
611use some::path::$0to::{Item};
612 "#,
613 r#"
614use some::path::{to::{Item}};
615 "#,
616 );
617 type_char(
618 '{',
619 r#"
620use $0Thing as _;
621 "#,
622 r#"
623use {Thing as _};
624 "#,
625 );
626
627 type_char_noop(
628 '{',
629 r#"
630use some::pa$0th::to::Item;
631 "#,
632 );
633 }
522} 634}
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index 7d2db201a..81c4d95b1 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -54,6 +54,14 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text
54 cov_mark::hit!(indent_block_contents); 54 cov_mark::hit!(indent_block_contents);
55 return Some(edit); 55 return Some(edit);
56 } 56 }
57
58 // Typing enter after the `{` of a use tree list.
59 if let Some(edit) = find_node_at_offset(file.syntax(), position.offset - TextSize::of('{'))
60 .and_then(|list| on_enter_in_use_tree_list(list, position))
61 {
62 cov_mark::hit!(indent_block_contents);
63 return Some(edit);
64 }
57 } 65 }
58 66
59 None 67 None
@@ -111,6 +119,21 @@ fn on_enter_in_block(block: ast::BlockExpr, position: FilePosition) -> Option<Te
111 Some(edit) 119 Some(edit)
112} 120}
113 121
122fn on_enter_in_use_tree_list(list: ast::UseTreeList, position: FilePosition) -> Option<TextEdit> {
123 if list.syntax().text().contains_char('\n') {
124 return None;
125 }
126
127 let indent = IndentLevel::from_node(list.syntax());
128 let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1));
129 edit.union(TextEdit::insert(
130 list.r_curly_token()?.text_range().start(),
131 format!("\n{}", indent),
132 ))
133 .ok()?;
134 Some(edit)
135}
136
114fn block_contents(block: &ast::BlockExpr) -> Option<SyntaxNode> { 137fn block_contents(block: &ast::BlockExpr) -> Option<SyntaxNode> {
115 let mut node = block.tail_expr().map(|e| e.syntax().clone()); 138 let mut node = block.tail_expr().map(|e| e.syntax().clone());
116 139
@@ -484,4 +507,96 @@ fn f() {$0
484 "#, 507 "#,
485 ); 508 );
486 } 509 }
510
511 #[test]
512 fn indents_use_tree_list() {
513 do_check(
514 r#"
515use crate::{$0};
516 "#,
517 r#"
518use crate::{
519 $0
520};
521 "#,
522 );
523 do_check(
524 r#"
525use crate::{$0Object, path::to::OtherThing};
526 "#,
527 r#"
528use crate::{
529 $0Object, path::to::OtherThing
530};
531 "#,
532 );
533 do_check(
534 r#"
535use {crate::{$0Object, path::to::OtherThing}};
536 "#,
537 r#"
538use {crate::{
539 $0Object, path::to::OtherThing
540}};
541 "#,
542 );
543 do_check(
544 r#"
545use {
546 crate::{$0Object, path::to::OtherThing}
547};
548 "#,
549 r#"
550use {
551 crate::{
552 $0Object, path::to::OtherThing
553 }
554};
555 "#,
556 );
557 }
558
559 #[test]
560 fn does_not_indent_use_tree_list_when_not_at_curly_brace() {
561 do_check_noop(
562 r#"
563use path::{Thing$0};
564 "#,
565 );
566 }
567
568 #[test]
569 fn does_not_indent_use_tree_list_without_curly_braces() {
570 do_check_noop(
571 r#"
572use path::Thing$0;
573 "#,
574 );
575 do_check_noop(
576 r#"
577use path::$0Thing;
578 "#,
579 );
580 do_check_noop(
581 r#"
582use path::Thing$0};
583 "#,
584 );
585 do_check_noop(
586 r#"
587use path::{$0Thing;
588 "#,
589 );
590 }
591
592 #[test]
593 fn does_not_indent_multiline_use_tree_list() {
594 do_check_noop(
595 r#"
596use path::{$0
597 Thing
598};
599 "#,
600 );
601 }
487} 602}