Skip to content

Commit 84c78a4

Browse files
committed
get to acceptable state
1 parent 718f7eb commit 84c78a4

File tree

2 files changed

+79
-133
lines changed

2 files changed

+79
-133
lines changed

crates/roughly/src/definition.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ fn try_get_identifier<'tree>(tree: &'tree Tree, line: usize, col: usize) -> Opti
9898
(start.kind_id() == kind::IDENTIFIER).then_some(start)
9999
}
100100

101-
fn find_previous_definition<'a>(start: Node<'a>, rope: &Rope, name: &str) -> Option<Node<'a>> {
101+
pub fn find_previous_definition<'a>(start: Node<'a>, rope: &Rope, name: &str) -> Option<Node<'a>> {
102102
let mut node = start;
103103
loop {
104104
let is_descendent;

crates/roughly/src/rename.rs

Lines changed: 78 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
use {
22
crate::{
3+
definition,
34
lsp_types::{TextEdit, Url as Uri, WorkspaceEdit},
45
tree::{self, field, kind},
56
utils,
67
},
78
ropey::Rope,
8-
std::collections::HashMap,
9+
std::collections::{HashMap, HashSet},
910
tree_sitter::{Node, Point, Tree},
1011
};
1112

13+
// 1. figure out if expression at position can be renamed (is identifier and not rhs of @ or $)
14+
// 2. find previous definition for identifier
15+
// 3. if identifier is global scope -> stop (for now, we would to rename in all files)
16+
// 4. find all references for identifier
17+
1218
pub fn rename(
1319
uri: &Uri,
1420
line: usize,
@@ -19,60 +25,58 @@ pub fn rename(
1925
) -> Option<WorkspaceEdit> {
2026
let start = try_get_identifier(tree, line, col)?;
2127
let parent = start.parent()?;
22-
let current_name = rope.byte_slice(start.byte_range()).to_string();
28+
let name = rope.byte_slice(start.byte_range()).to_string();
2329

2430
tracing::debug!(
25-
?current_name,
31+
?name,
2632
?new_name,
2733
start = start.kind(),
2834
parent = parent.kind(),
2935
"rename request"
3036
);
3137

32-
// Check if rename is allowed - don't allow renaming RHS of certain operators
3338
if !is_rename_allowed(start, parent) {
3439
tracing::debug!("Rename not allowed for this symbol");
3540
return None;
3641
}
3742

38-
// Find all references to this symbol in the appropriate scope
39-
let references = find_all_references(start, rope, &current_name)?;
40-
41-
// Convert references to text edits
42-
let mut edits = Vec::new();
43-
for reference in references {
44-
let range = utils::node_range(reference);
45-
edits.push(TextEdit {
46-
range,
47-
new_text: new_name.to_string(),
48-
});
49-
}
43+
let definition = definition::find_previous_definition(start, rope, &name)?;
44+
let scope = find_scope_containing(definition)?; // for now we return if global scope
45+
46+
let mut references = Vec::new();
47+
find_references_in_scope(scope, rope, &name, definition, &mut references);
48+
49+
let edits: Vec<_> = references
50+
.into_iter()
51+
.filter_map({
52+
let mut seen = HashSet::new(); // make sure we don't edit same node multiple times
53+
move |reference| {
54+
seen.insert(reference.id()).then(|| TextEdit {
55+
range: utils::node_range(reference),
56+
new_text: new_name.to_string(),
57+
})
58+
}
59+
})
60+
.collect();
5061

5162
if edits.is_empty() {
5263
return None;
5364
}
5465

55-
// Create workspace edit
56-
let mut changes = HashMap::new();
57-
changes.insert(uri.clone(), edits);
66+
tracing::debug!(?edits);
5867

5968
Some(WorkspaceEdit {
60-
changes: Some(changes),
69+
changes: Some(HashMap::from_iter([(uri.clone(), edits)])),
6170
..Default::default()
6271
})
6372
}
6473

6574
fn is_rename_allowed(symbol: Node, parent: Node) -> bool {
6675
// Don't allow renaming RHS of extract @, extract $, or namespace :: operators
67-
if [kind::EXTRACT_OPERATOR, kind::NAMESPACE_OPERATOR].contains(&parent.kind_id())
76+
!([kind::EXTRACT_OPERATOR, kind::NAMESPACE_OPERATOR].contains(&parent.kind_id())
6877
&& parent
6978
.child_by_field_id(field::RHS)
70-
.is_some_and(|rhs| rhs.id() == symbol.id())
71-
{
72-
return false;
73-
}
74-
75-
true
79+
.is_some_and(|rhs| rhs.id() == symbol.id()))
7680
}
7781

7882
fn try_get_identifier<'tree>(tree: &'tree Tree, line: usize, col: usize) -> Option<Node<'tree>> {
@@ -94,82 +98,9 @@ fn try_get_identifier<'tree>(tree: &'tree Tree, line: usize, col: usize) -> Opti
9498
(start.kind_id() == kind::IDENTIFIER).then_some(start)
9599
}
96100

97-
/// Find all references to a symbol within its scope
98-
fn find_all_references<'a>(start: Node<'a>, rope: &Rope, name: &str) -> Option<Vec<Node<'a>>> {
99-
let mut references = Vec::new();
100-
101-
// Find the definition of the symbol
102-
let definition = find_definition(start, rope, name)?;
103-
104-
// Find the scope containing the definition
105-
let scope = find_scope_containing(definition)?;
106-
107-
// Find all references within this scope
108-
find_references_in_scope(scope, rope, name, definition, &mut references);
109-
110-
// Deduplicate references by node ID
111-
let mut unique_refs = Vec::new();
112-
let mut seen_ids = std::collections::HashSet::new();
113-
114-
for reference in references {
115-
if seen_ids.insert(reference.id()) {
116-
unique_refs.push(reference);
117-
}
118-
}
119-
120-
Some(unique_refs)
121-
}
122-
123-
fn find_definition<'a>(start: Node<'a>, rope: &Rope, name: &str) -> Option<Node<'a>> {
124-
let mut node = start;
125-
loop {
126-
let is_descendent;
127-
if let Some(sibling) = node.prev_sibling() {
128-
node = sibling;
129-
is_descendent = false;
130-
} else if let Some(parent) = node.parent() {
131-
node = parent;
132-
is_descendent = true;
133-
} else {
134-
break None;
135-
}
136-
137-
let maybe_definition = match node.kind_id() {
138-
kind::PARAMETERS => node
139-
.children_by_field_name("parameter", &mut node.walk())
140-
.filter_map(|parameter| parameter.child_by_field_id(field::NAME))
141-
.find(|child| rope.byte_slice(child.byte_range()) == name),
142-
kind::BINARY_OPERATOR => {
143-
let maybe_lhs = node.child_by_field_id(field::LHS);
144-
let maybe_op = node.child_by_field_id(field::OPERATOR);
145-
maybe_lhs.filter(|lhs| {
146-
lhs.kind_id() == kind::IDENTIFIER
147-
&& maybe_op.is_some_and(|op| {
148-
[kind::EQUAL, kind::LEFT_ASSIGN].contains(&op.kind_id())
149-
})
150-
&& rope.byte_slice(lhs.byte_range()) == name
151-
&& (!is_descendent || lhs.id() == start.id())
152-
})
153-
}
154-
_ => None,
155-
};
156-
157-
if maybe_definition.is_some() {
158-
return maybe_definition;
159-
}
160-
}
161-
}
162-
163-
fn find_scope_containing<'a>(node: Node<'a>) -> Option<Node<'a>> {
164-
let mut current = Some(node);
165-
while let Some(node) = current {
166-
match node.kind_id() {
167-
kind::FUNCTION_DEFINITION => return Some(node),
168-
kind::PROGRAM => return Some(node),
169-
_ => current = node.parent(),
170-
}
171-
}
172-
None
101+
fn find_scope_containing(node: Node) -> Option<Node> {
102+
std::iter::successors(Some(node), |node| node.parent())
103+
.find(|node| node.kind_id() == kind::FUNCTION_DEFINITION)
173104
}
174105

175106
fn find_references_in_scope<'a>(
@@ -186,47 +117,41 @@ fn find_references_in_scope<'a>(
186117
// If this is an identifier with matching name, check if it refers to our definition
187118
if node.kind_id() == kind::IDENTIFIER
188119
&& rope.byte_slice(node.byte_range()) == name
189-
&& is_reference_to_symbol(node, rope, name)
120+
// todo consider moving this check up
121+
&& (node
122+
.parent()
123+
.is_none_or(|parent| is_rename_allowed(node, parent)))
190124
&& refers_to_definition(node, definition, rope, name)
191125
{
192126
references.push(node);
193127
}
194128

195-
// Add children to queue for processing
196-
let mut child_cursor = node.walk();
197-
if child_cursor.goto_first_child() {
129+
let mut cursor = node.walk();
130+
if cursor.goto_first_child() {
198131
loop {
199-
let child = child_cursor.node();
132+
let child = cursor.node();
200133

201134
// We need to traverse nested functions too for free variables
202135
// But we need to be careful about scope - we'll let the definition
203136
// finding logic handle whether a reference is valid
204137
queue.push(child);
205138

206-
if !child_cursor.goto_next_sibling() {
139+
if !cursor.goto_next_sibling() {
207140
break;
208141
}
209142
}
210143
}
211144
}
212145
}
213146

214-
fn is_reference_to_symbol(node: Node, rope: &Rope, name: &str) -> bool {
215-
node.kind_id() == kind::IDENTIFIER
216-
&& rope.byte_slice(node.byte_range()) == name
217-
&& (node
218-
.parent()
219-
.is_none_or(|parent| is_rename_allowed(node, parent)))
220-
}
221-
222147
fn refers_to_definition(reference: Node, definition: Node, rope: &Rope, name: &str) -> bool {
223148
// If this is the definition itself, include it
224149
if reference.id() == definition.id() {
225150
return true;
226151
}
227152

228153
// Use the same logic as find_definition to see if this reference would resolve to our definition
229-
let found_definition = find_definition(reference, rope, name);
154+
let found_definition = definition::find_previous_definition(reference, rope, name);
230155
match found_definition {
231156
Some(found_def) => found_def.id() == definition.id(),
232157
None => false,
@@ -239,21 +164,41 @@ mod tests {
239164

240165
fn setup(src: &str, line: usize, col: usize, new_name: &str) -> Option<WorkspaceEdit> {
241166
let rope = Rope::from_str(src);
242-
let mut parser = tree::new_parser();
243-
let tree = tree::parse(&mut parser, src, None);
167+
let tree = tree::parse(&mut tree::new_parser(), src, None);
244168
let uri = Uri::parse("file:///test.R").unwrap();
245169
rename(&uri, line, col, new_name, &rope, &tree)
246170
}
247171

172+
// #[test]
173+
// fn global_variable_rename() {
174+
// let src = indoc! {r#"
175+
// x <- 1
176+
// y <- x + 2
177+
// x
178+
// "#};
179+
180+
// let result = setup(src, 0, 0, "new_var").unwrap();
181+
// let changes = result.changes.unwrap();
182+
// let uri = Uri::parse("file:///test.R").unwrap();
183+
// let edits = &changes[&uri];
184+
185+
// assert_eq!(edits.len(), 3); // Should find 3 references
186+
// assert_eq!(edits[0].new_text, "new_var");
187+
// assert_eq!(edits[1].new_text, "new_var");
188+
// assert_eq!(edits[2].new_text, "new_var");
189+
// }
190+
248191
#[test]
249-
fn basic_variable_rename() {
192+
fn local_variable_rename() {
250193
let src = indoc! {r#"
251-
x <- 1
252-
y <- x + 2
253-
x
194+
function() {
195+
x <- 1
196+
y <- x + 2
197+
x
198+
}
254199
"#};
255200

256-
let result = setup(src, 0, 0, "new_var").unwrap();
201+
let result = setup(src, 2, 9, "new_var").unwrap();
257202
let changes = result.changes.unwrap();
258203
let uri = Uri::parse("file:///test.R").unwrap();
259204
let edits = &changes[&uri];
@@ -263,7 +208,6 @@ mod tests {
263208
assert_eq!(edits[1].new_text, "new_var");
264209
assert_eq!(edits[2].new_text, "new_var");
265210
}
266-
267211
#[test]
268212
fn parameter_rename() {
269213
let src = indoc! {r#"
@@ -367,17 +311,19 @@ mod tests {
367311
}
368312

369313
#[test]
370-
fn shadowing_variables() {
314+
fn shadowing_local_variables() {
371315
let src = indoc! {r#"
372-
x <- 1
373316
function() {
374-
x <- 2
375-
x
317+
x <- 1
318+
function() {
319+
x <- 2
320+
x
321+
}
376322
}
377323
"#};
378324

379325
// Rename outer x - should only affect outer scope
380-
let result = setup(src, 0, 0, "outer_x").unwrap();
326+
let result = setup(src, 1, 4, "outer_x").unwrap();
381327
let changes = result.changes.unwrap();
382328
let uri = Uri::parse("file:///test.R").unwrap();
383329
let edits = &changes[&uri];
@@ -408,7 +354,7 @@ mod tests {
408354
fn variable_in_control_structures() {
409355
let src = indoc! {r#"
410356
x <- 1
411-
if (TRUE) {
357+
if (condition) {
412358
x <- 2
413359
x
414360
}

0 commit comments

Comments
 (0)