Skip to content

Commit 83b2d78

Browse files
committed
Supporting extend selection inside macro calls
1 parent 8bb2a50 commit 83b2d78

File tree

1 file changed

+109
-14
lines changed

1 file changed

+109
-14
lines changed

crates/ra_ide/src/extend_selection.rs

Lines changed: 109 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,27 @@ use ra_db::SourceDatabase;
44
use ra_syntax::{
55
algo::find_covering_element,
66
ast::{self, AstNode, AstToken},
7-
Direction, NodeOrToken,
7+
Direction, NodeOrToken, SyntaxElement,
88
SyntaxKind::{self, *},
99
SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T,
1010
};
1111

1212
use crate::{db::RootDatabase, FileRange};
13+
use hir::{db::AstDatabase, InFile};
14+
use itertools::Itertools;
1315

1416
// FIXME: restore macro support
1517
pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
16-
let parse = db.parse(frange.file_id);
17-
try_extend_selection(parse.tree().syntax(), frange.range).unwrap_or(frange.range)
18+
let src = db.parse(frange.file_id).tree();
19+
let root = InFile::new(frange.file_id.into(), src.syntax());
20+
try_extend_selection(db, root, frange.range).unwrap_or(frange.range)
1821
}
1922

20-
fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> {
23+
fn try_extend_selection(
24+
db: &RootDatabase,
25+
root: InFile<&SyntaxNode>,
26+
range: TextRange,
27+
) -> Option<TextRange> {
2128
let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
2229
let list_kinds = [
2330
RECORD_FIELD_PAT_LIST,
@@ -40,9 +47,9 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
4047

4148
if range.is_empty() {
4249
let offset = range.start();
43-
let mut leaves = root.token_at_offset(offset);
50+
let mut leaves = root.value.token_at_offset(offset);
4451
if leaves.clone().all(|it| it.kind() == WHITESPACE) {
45-
return Some(extend_ws(root, leaves.next()?, offset));
52+
return Some(extend_ws(root.value, leaves.next()?, offset));
4653
}
4754
let leaf_range = match leaves {
4855
TokenAtOffset::None => return None,
@@ -58,7 +65,7 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
5865
};
5966
return Some(leaf_range);
6067
};
61-
let node = match find_covering_element(root, range) {
68+
let node = match find_covering_element(root.value, range) {
6269
NodeOrToken::Token(token) => {
6370
if token.text_range() != range {
6471
return Some(token.text_range());
@@ -72,6 +79,16 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
7279
}
7380
NodeOrToken::Node(node) => node,
7481
};
82+
83+
// if we are in single token_tree, we maybe live in macro or attr
84+
if node.kind() == TOKEN_TREE {
85+
if let Some(macro_call) = node.ancestors().find_map(ast::MacroCall::cast) {
86+
if let Some(range) = extend_tokens_from_range(db, &root, macro_call, range) {
87+
return Some(range);
88+
}
89+
}
90+
}
91+
7592
if node.text_range() != range {
7693
return Some(node.text_range());
7794
}
@@ -88,6 +105,67 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
88105
node.parent().map(|it| it.text_range())
89106
}
90107

108+
fn extend_tokens_from_range(
109+
db: &RootDatabase,
110+
root: &InFile<&SyntaxNode>,
111+
macro_call: ast::MacroCall,
112+
original_range: TextRange,
113+
) -> Option<TextRange> {
114+
let analyzer = hir::SourceAnalyzer::new(db, root.clone(), None);
115+
let expansion = analyzer.expand(db, root.with_value(&macro_call))?;
116+
117+
// compute original mapped token range
118+
let range = macro_call
119+
.syntax()
120+
.descendants_with_tokens()
121+
.filter_map(|n| match n {
122+
NodeOrToken::Token(token) if token.text_range().is_subrange(&original_range) => {
123+
expansion
124+
.map_token_down(db, root.with_value(&token))
125+
.map(|node| node.value.text_range())
126+
}
127+
_ => None,
128+
})
129+
.fold1(|x, y| union_range(x, y))?;
130+
131+
let src = db.parse_or_expand(expansion.file_id())?;
132+
let parent = shallow_node(&find_covering_element(&src, range))?.parent()?;
133+
134+
// compute parent mapped token range
135+
let range = macro_call
136+
.syntax()
137+
.descendants_with_tokens()
138+
.filter_map(|n| match n {
139+
NodeOrToken::Token(token) => {
140+
expansion.map_token_down(db, root.with_value(&token)).and_then(|node| {
141+
if node.value.text_range().is_subrange(&parent.text_range()) {
142+
Some(token.text_range())
143+
} else {
144+
None
145+
}
146+
})
147+
}
148+
_ => None,
149+
})
150+
.fold1(|x, y| union_range(x, y))?;
151+
152+
if original_range.is_subrange(&range) && original_range != range {
153+
Some(range)
154+
} else {
155+
None
156+
}
157+
}
158+
159+
fn union_range(range: TextRange, r: TextRange) -> TextRange {
160+
let start = range.start().min(r.start());
161+
let end = range.end().max(r.end());
162+
TextRange::from_to(start, end)
163+
}
164+
165+
fn shallow_node(node: &SyntaxElement) -> Option<SyntaxNode> {
166+
node.ancestors().take_while(|n| n.text_range() == node.text_range()).last()
167+
}
168+
91169
fn extend_single_word_in_comment_or_string(
92170
leaf: &SyntaxToken,
93171
offset: TextUnit,
@@ -227,18 +305,19 @@ fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
227305

228306
#[cfg(test)]
229307
mod tests {
230-
use ra_syntax::{AstNode, SourceFile};
231-
use test_utils::extract_offset;
232-
233308
use super::*;
309+
use crate::mock_analysis::single_file;
310+
use test_utils::extract_offset;
234311

235312
fn do_check(before: &str, afters: &[&str]) {
236313
let (cursor, before) = extract_offset(before);
237-
let parse = SourceFile::parse(&before);
238-
let mut range = TextRange::offset_len(cursor, 0.into());
314+
let (analysis, file_id) = single_file(&before);
315+
let range = TextRange::offset_len(cursor, 0.into());
316+
let mut frange = FileRange { file_id: file_id, range };
317+
239318
for &after in afters {
240-
range = try_extend_selection(parse.tree().syntax(), range).unwrap();
241-
let actual = &before[range];
319+
frange.range = analysis.extend_selection(frange).unwrap();
320+
let actual = &before[frange.range];
242321
assert_eq!(after, actual);
243322
}
244323
}
@@ -503,4 +582,20 @@ fn main() { let var = (
503582
],
504583
);
505584
}
585+
586+
#[test]
587+
fn extend_selection_inside_macros() {
588+
do_check(
589+
r#"macro_rules! foo { ($item:item) => {$item} }
590+
foo!{fn hello(na<|>me:usize){}}"#,
591+
&[
592+
"name",
593+
"name:usize",
594+
"(name:usize)",
595+
"fn hello(name:usize){}",
596+
"{fn hello(name:usize){}}",
597+
"foo!{fn hello(name:usize){}}",
598+
],
599+
);
600+
}
506601
}

0 commit comments

Comments
 (0)