Skip to content

Commit 391ccb9

Browse files
feat: Add precedence in autocompletion (#51)
Co-authored-by: Felix Andreas <[email protected]>
1 parent f74a5a2 commit 391ccb9

File tree

1 file changed

+111
-47
lines changed

1 file changed

+111
-47
lines changed

crates/roughly/src/completion.rs

Lines changed: 111 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ pub fn get(
5757
completions
5858
.into_iter()
5959
.map(|item| CompletionItem {
60-
label: item,
60+
label: item.clone(),
61+
sort_text: Some("1".into()),
6162
..Default::default()
6263
})
6364
.collect(),
@@ -107,6 +108,7 @@ pub fn get(
107108
description: Some("Keyword".into()),
108109
}),
109110
kind: Some(CompletionItemKind::KEYWORD),
111+
sort_text: Some("0".into()),
110112
..Default::default()
111113
})
112114
};
@@ -129,55 +131,67 @@ pub fn get(
129131
description: Some("Global".into()),
130132
}),
131133
kind: Some(to_completion_kind(&symbol.info)),
134+
sort_text: Some("2".into()),
132135
..Default::default()
133136
});
134137

135-
let local_symbols: Vec<CompletionItem> =
136-
{
137-
let point = Point::new(position.line as usize, position.character as usize);
138-
tree.root_node()
139-
.descendant_for_point_range(point, point)
140-
.map(|node| {
141-
std::iter::successors(Some(node), |node| node.parent())
142-
// note: we just search functions, as global symbols are already included from workspace info
143-
.filter(|node| node.kind_id() == kind::FUNCTION_DEFINITION)
144-
.flat_map(|node| {
145-
let mut items = Vec::new();
146-
147-
if let Some(parameters) = node.child_by_field_id(field::PARAMETERS) {
148-
items.extend(
149-
parameters
150-
.children_by_field_name("parameter", &mut parameters.walk())
151-
.filter_map(|parameter| {
152-
parameter.child_by_field_id(field::NAME).map(|name| {
153-
rope.byte_slice(name.byte_range()).to_string()
154-
})
138+
let local_symbols: Vec<CompletionItem> = {
139+
let point = Point::new(position.line as usize, position.character as usize);
140+
tree.root_node()
141+
.descendant_for_point_range(point, point)
142+
.map(|node| {
143+
std::iter::successors(Some(node), |node| node.parent())
144+
// note: we just search functions, as global symbols are already included from workspace info
145+
.filter(|node| node.kind_id() == kind::FUNCTION_DEFINITION)
146+
.flat_map(|node| {
147+
let mut items = Vec::new();
148+
149+
if let Some(parameters) = node.child_by_field_id(field::PARAMETERS) {
150+
items.extend(
151+
parameters
152+
.children_by_field_name("parameter", &mut parameters.walk())
153+
.filter_map(|parameter| {
154+
parameter.child_by_field_id(field::NAME).map(|name| {
155+
rope.byte_slice(name.byte_range()).to_string()
155156
})
156-
.filter(|name| utils::starts_with_lowercase(name, &query))
157-
.map(|label| CompletionItem {
158-
label,
159-
label_details: Some(CompletionItemLabelDetails {
160-
detail: None,
161-
description: Some("Parameter".into()),
162-
}),
163-
kind: Some(CompletionItemKind::VARIABLE),
164-
..Default::default()
157+
})
158+
.filter(|name| utils::starts_with_lowercase(name, &query))
159+
.map(|label| CompletionItem {
160+
label: label.clone(),
161+
label_details: Some(CompletionItemLabelDetails {
162+
detail: None,
163+
description: Some("Parameter".into()),
165164
}),
166-
);
167-
}
168-
169-
if let Some(body) = node.child_by_field_id(field::BODY) {
170-
items.extend(locals_completion(body, rope).into_iter().filter(
171-
|item| utils::starts_with_lowercase(&item.label, &query),
172-
));
173-
}
174-
175-
items
176-
})
177-
.collect()
178-
})
179-
.unwrap_or_default()
180-
};
165+
kind: Some(CompletionItemKind::VARIABLE),
166+
sort_text: Some("1".into()),
167+
..Default::default()
168+
}),
169+
);
170+
}
171+
172+
if let Some(body) = node.child_by_field_id(field::BODY) {
173+
items.extend(
174+
locals_completion(body, rope)
175+
.into_iter()
176+
.filter(|item| {
177+
utils::starts_with_lowercase(&item.label, &query)
178+
})
179+
.map(|mut item| {
180+
// Update sort_text for local variables to ensure consistent precedence
181+
if let Some(sort_text) = &item.sort_text {
182+
item.sort_text = Some(sort_text.clone());
183+
}
184+
item
185+
}),
186+
);
187+
}
188+
189+
items
190+
})
191+
.collect()
192+
})
193+
.unwrap_or_default()
194+
};
181195

182196
Some(CompletionResponse::Array(
183197
keyword_symbols
@@ -247,12 +261,13 @@ pub fn locals_completion(node: Node, rope: &Rope) -> Vec<CompletionItem> {
247261
{
248262
let label = rope.byte_slice(lhs.byte_range()).to_string();
249263
symbols.push(CompletionItem {
250-
label,
264+
label: label.clone(),
251265
label_details: Some(CompletionItemLabelDetails {
252266
detail: None,
253267
description: Some("Local".into()),
254268
}),
255269
kind: Some(CompletionItemKind::VARIABLE),
270+
sort_text: Some("1".into()),
256271
..Default::default()
257272
})
258273
}
@@ -274,7 +289,13 @@ const NAMESPACE_QUERY: &str = r#"(namespace_operator rhs: (identifier) @ident)"#
274289

275290
#[cfg(test)]
276291
mod tests {
277-
use {super::*, crate::tree, indoc::indoc, ropey::Rope, std::collections::HashMap};
292+
use {
293+
super::*,
294+
crate::{index::Item, lsp_types::Range, tree},
295+
indoc::indoc,
296+
ropey::Rope,
297+
std::collections::HashMap,
298+
};
278299

279300
fn setup(
280301
text: &str,
@@ -567,4 +588,47 @@ mod tests {
567588
let _item = Query::new(&tree_sitter_r::LANGUAGE.into(), ITEM_QUERY).unwrap();
568589
let _namespace = Query::new(&tree_sitter_r::LANGUAGE.into(), NAMESPACE_QUERY).unwrap();
569590
}
591+
592+
#[test]
593+
fn local_symbols_have_higher_precedence_than_global() {
594+
let rope = Rope::from_str(indoc! {"
595+
function(x) {
596+
var_local <- 1
597+
var_
598+
}
599+
"});
600+
601+
let tree = tree::parse(&mut tree::new_parser(), rope.to_string().as_str(), None);
602+
let position = Position::new(2, 8);
603+
604+
let symbols_map = HashMap::from_iter([(
605+
std::path::PathBuf::from("test.R"),
606+
vec![Item {
607+
name: "var_global".to_string(),
608+
detail: None,
609+
range: Range::new(Position::new(0, 0), Position::new(0, 10)),
610+
selection_range: Range::new(Position::new(0, 0), Position::new(0, 10)),
611+
children: None,
612+
info: ItemInfo::Function,
613+
}],
614+
)]);
615+
616+
let completion_response = get(position, &rope, &tree, &symbols_map).unwrap();
617+
618+
let CompletionResponse::Array(items) = completion_response else {
619+
unreachable!();
620+
};
621+
622+
let local_item = items.iter().find(|item| item.label == "var_local").unwrap();
623+
let global_item = items
624+
.iter()
625+
.find(|item| item.label == "var_global")
626+
.unwrap();
627+
628+
let local_sort = local_item.sort_text.as_ref().unwrap();
629+
let global_sort = global_item.sort_text.as_ref().unwrap();
630+
631+
assert!(local_sort.starts_with("1"));
632+
assert!(global_sort.starts_with("2"));
633+
}
570634
}

0 commit comments

Comments
 (0)