@@ -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) ]
276291mod 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