Skip to content

Commit be9e7f9

Browse files
bors[bot]Veykril
andauthored
Merge #10528
10528: internal: Make selections in assists with trailing/leading whitespace more forgiving r=Veykril a=Veykril bors r+ Co-authored-by: Lukas Wirth <[email protected]>
2 parents 64b1c72 + 03fcf1b commit be9e7f9

14 files changed

+57
-23
lines changed

crates/ide_assists/src/assist_context.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use ide_db::{
1414
};
1515
use syntax::{
1616
algo::{self, find_node_at_offset, find_node_at_range},
17-
AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr,
17+
AstNode, AstToken, Direction, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr,
1818
SyntaxToken, TextRange, TextSize, TokenAtOffset,
1919
};
2020
use text_edit::{TextEdit, TextEditBuilder};
@@ -57,6 +57,7 @@ pub(crate) struct AssistContext<'a> {
5757
pub(crate) config: &'a AssistConfig,
5858
pub(crate) sema: Semantics<'a, RootDatabase>,
5959
pub(crate) frange: FileRange,
60+
trimmed_range: TextRange,
6061
source_file: SourceFile,
6162
}
6263

@@ -67,7 +68,20 @@ impl<'a> AssistContext<'a> {
6768
frange: FileRange,
6869
) -> AssistContext<'a> {
6970
let source_file = sema.parse(frange.file_id);
70-
AssistContext { config, sema, frange, source_file }
71+
72+
let start = frange.range.start();
73+
let end = frange.range.end();
74+
let left = source_file.syntax().token_at_offset(start);
75+
let right = source_file.syntax().token_at_offset(end);
76+
let left =
77+
left.right_biased().and_then(|t| algo::skip_whitespace_token(t, Direction::Next));
78+
let right =
79+
right.left_biased().and_then(|t| algo::skip_whitespace_token(t, Direction::Prev));
80+
let left = left.map(|t| t.text_range().start()).unwrap_or(start).clamp(start, end);
81+
let right = right.map(|t| t.text_range().end()).unwrap_or(end).clamp(start, end);
82+
let trimmed_range = TextRange::new(left, right);
83+
84+
AssistContext { config, sema, frange, source_file, trimmed_range }
7185
}
7286

7387
pub(crate) fn db(&self) -> &RootDatabase {
@@ -79,6 +93,12 @@ impl<'a> AssistContext<'a> {
7993
self.frange.range.start()
8094
}
8195

96+
/// Returns the selected range trimmed for whitespace tokens, that is the range will be snapped
97+
/// to the nearest enclosed token.
98+
pub(crate) fn selection_trimmed(&self) -> TextRange {
99+
self.trimmed_range
100+
}
101+
82102
pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
83103
self.source_file.syntax().token_at_offset(self.offset())
84104
}
@@ -92,13 +112,15 @@ impl<'a> AssistContext<'a> {
92112
find_node_at_offset(self.source_file.syntax(), self.offset())
93113
}
94114
pub(crate) fn find_node_at_range<N: AstNode>(&self) -> Option<N> {
95-
find_node_at_range(self.source_file.syntax(), self.frange.range)
115+
find_node_at_range(self.source_file.syntax(), self.trimmed_range)
96116
}
97117
pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
98118
self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
99119
}
120+
/// Returns the element covered by the selection range, this excludes trailing whitespace in the selection.
100121
pub(crate) fn covering_element(&self) -> SyntaxElement {
101-
self.source_file.syntax().covering_element(self.frange.range)
122+
self.source_file.syntax().covering_element(self.selection_trimmed())
123+
// self.source_file.syntax().covering_element(self.frange.range)
102124
}
103125
}
104126

crates/ide_assists/src/handlers/add_return_type.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,11 @@ fn extract_tail(ctx: &AssistContext) -> Option<(FnType, ast::Expr, InsertOrRepla
107107
let ret_range = TextRange::new(rparen_pos, ret_range_end);
108108
(FnType::Function, tail_expr, ret_range, action)
109109
};
110-
let frange = ctx.frange.range;
111-
if return_type_range.contains_range(frange) {
110+
let range = ctx.selection_trimmed();
111+
if return_type_range.contains_range(range) {
112112
cov_mark::hit!(cursor_in_ret_position);
113113
cov_mark::hit!(cursor_in_ret_position_closure);
114-
} else if tail_expr.syntax().text_range().contains_range(frange) {
114+
} else if tail_expr.syntax().text_range().contains_range(range) {
115115
cov_mark::hit!(cursor_on_tail);
116116
cov_mark::hit!(cursor_on_tail_closure);
117117
} else {

crates/ide_assists/src/handlers/apply_demorgan.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
3333
_ => return None,
3434
};
3535

36-
let cursor_in_range = op_range.contains_range(ctx.frange.range);
36+
let cursor_in_range = op_range.contains_range(ctx.selection_trimmed());
3737
if !cursor_in_range {
3838
return None;
3939
}

crates/ide_assists/src/handlers/convert_iter_for_each_to_for.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ fn validate_method_call_expr(
199199
expr: ast::MethodCallExpr,
200200
) -> Option<(ast::Expr, ast::Expr)> {
201201
let name_ref = expr.name_ref()?;
202-
if name_ref.syntax().text_range().intersect(ctx.frange.range).is_none() {
202+
if !name_ref.syntax().text_range().contains_range(ctx.selection_trimmed()) {
203203
cov_mark::hit!(test_for_each_not_applicable_invalid_cursor_pos);
204204
return None;
205205
}

crates/ide_assists/src/handlers/extract_function.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ type FxIndexSet<T> = indexmap::IndexSet<T, BuildHasherDefault<FxHasher>>;
5656
// }
5757
// ```
5858
pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
59-
let range = ctx.frange.range;
59+
let range = ctx.selection_trimmed();
6060
if range.is_empty() {
6161
return None;
6262
}

crates/ide_assists/src/handlers/extract_variable.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
3131
if ctx.frange.range.is_empty() {
3232
return None;
3333
}
34+
3435
let node = match ctx.covering_element() {
3536
NodeOrToken::Node(it) => it,
3637
NodeOrToken::Token(it) if it.kind() == COMMENT => {
@@ -238,7 +239,7 @@ fn foo() {
238239
extract_variable,
239240
r#"
240241
fn foo() {
241-
$01 + 1$0;
242+
$0 1 + 1$0;
242243
}"#,
243244
r#"
244245
fn foo() {
@@ -247,12 +248,12 @@ fn foo() {
247248
);
248249
check_assist(
249250
extract_variable,
250-
"
251+
r"
251252
fn foo() {
252253
$0{ let x = 0; x }$0
253254
something_else();
254255
}",
255-
"
256+
r"
256257
fn foo() {
257258
let $0var_name = { let x = 0; x };
258259
something_else();
@@ -264,11 +265,11 @@ fn foo() {
264265
fn test_extract_var_part_of_expr_stmt() {
265266
check_assist(
266267
extract_variable,
267-
"
268+
r"
268269
fn foo() {
269270
$01$0 + 1;
270271
}",
271-
"
272+
r"
272273
fn foo() {
273274
let $0var_name = 1;
274275
var_name + 1;

crates/ide_assists/src/handlers/flip_binexpr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
2323
let rhs = expr.rhs()?.syntax().clone();
2424
let op_range = expr.op_token()?.text_range();
2525
// The assist should be applied only if the cursor is on the operator
26-
let cursor_in_range = op_range.contains_range(ctx.frange.range);
26+
let cursor_in_range = op_range.contains_range(ctx.selection_trimmed());
2727
if !cursor_in_range {
2828
return None;
2929
}

crates/ide_assists/src/handlers/inline_call.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
199199
let param_list = fn_source.value.param_list()?;
200200

201201
let FileRange { file_id, range } = fn_source.syntax().original_file_range(ctx.sema.db);
202-
if file_id == ctx.frange.file_id && range.contains(ctx.frange.range.start()) {
202+
if file_id == ctx.frange.file_id && range.contains(ctx.offset()) {
203203
cov_mark::hit!(inline_call_recursive);
204204
return None;
205205
}

crates/ide_assists/src/handlers/inline_local_variable.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use either::Either;
22
use hir::{PathResolution, Semantics};
33
use ide_db::{
4-
base_db::{FileId, FileRange},
4+
base_db::FileId,
55
defs::Definition,
66
search::{FileReference, UsageSearchResult},
77
RootDatabase,
@@ -33,7 +33,8 @@ use crate::{
3333
// }
3434
// ```
3535
pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36-
let FileRange { file_id, range } = ctx.frange;
36+
let file_id = ctx.frange.file_id;
37+
let range = ctx.selection_trimmed();
3738
let InlineData { let_stmt, delete_let, references, target } =
3839
if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() {
3940
inline_let(&ctx.sema, let_stmt, range, file_id)

crates/ide_assists/src/handlers/invert_if.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
2929
let if_keyword = ctx.find_token_syntax_at_offset(T![if])?;
3030
let expr = ast::IfExpr::cast(if_keyword.parent()?)?;
3131
let if_range = if_keyword.text_range();
32-
let cursor_in_range = if_range.contains_range(ctx.frange.range);
32+
let cursor_in_range = if_range.contains_range(ctx.selection_trimmed());
3333
if !cursor_in_range {
3434
return None;
3535
}

crates/ide_assists/src/handlers/move_from_mod_rs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub(crate) fn move_from_mod_rs(acc: &mut Assists, ctx: &AssistContext) -> Option
2727
let source_file = ctx.find_node_at_offset::<ast::SourceFile>()?;
2828
let module = ctx.sema.to_module_def(ctx.frange.file_id)?;
2929
// Enable this assist if the user select all "meaningful" content in the source file
30-
let trimmed_selected_range = trimmed_text_range(&source_file, ctx.frange.range);
30+
let trimmed_selected_range = trimmed_text_range(&source_file, ctx.selection_trimmed());
3131
let trimmed_file_range = trimmed_text_range(&source_file, source_file.syntax().text_range());
3232
if !module.is_mod_rs(ctx.db()) {
3333
cov_mark::hit!(not_mod_rs);

crates/ide_assists/src/handlers/move_to_mod_rs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub(crate) fn move_to_mod_rs(acc: &mut Assists, ctx: &AssistContext) -> Option<(
2727
let source_file = ctx.find_node_at_offset::<ast::SourceFile>()?;
2828
let module = ctx.sema.to_module_def(ctx.frange.file_id)?;
2929
// Enable this assist if the user select all "meaningful" content in the source file
30-
let trimmed_selected_range = trimmed_text_range(&source_file, ctx.frange.range);
30+
let trimmed_selected_range = trimmed_text_range(&source_file, ctx.selection_trimmed());
3131
let trimmed_file_range = trimmed_text_range(&source_file, source_file.syntax().text_range());
3232
if module.is_mod_rs(ctx.db()) {
3333
cov_mark::hit!(already_mod_rs);

crates/ide_assists/src/handlers/replace_if_let_with_match.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
4848
if_expr.syntax().text_range().start(),
4949
if_expr.then_branch()?.syntax().text_range().start(),
5050
);
51-
let cursor_in_range = available_range.contains_range(ctx.frange.range);
51+
let cursor_in_range = available_range.contains_range(ctx.selection_trimmed());
5252
if !cursor_in_range {
5353
return None;
5454
}

crates/syntax/src/algo.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ pub fn skip_trivia_token(mut token: SyntaxToken, direction: Direction) -> Option
5353
}
5454
Some(token)
5555
}
56+
/// Skip to next non `whitespace` token
57+
pub fn skip_whitespace_token(mut token: SyntaxToken, direction: Direction) -> Option<SyntaxToken> {
58+
while token.kind() == SyntaxKind::WHITESPACE {
59+
token = match direction {
60+
Direction::Next => token.next_token()?,
61+
Direction::Prev => token.prev_token()?,
62+
}
63+
}
64+
Some(token)
65+
}
5666

5767
/// Finds the first sibling in the given direction which is not `trivia`
5868
pub fn non_trivia_sibling(element: SyntaxElement, direction: Direction) -> Option<SyntaxElement> {

0 commit comments

Comments
 (0)