@@ -21,6 +21,91 @@ use crate::importer::{ImportRequest, Importer};
2121use crate :: symbols:: QueryPattern ;
2222use crate :: { Db , all_symbols} ;
2323
24+ /// A collection of completions built up from various sources.
25+ #[ derive( Clone ) ]
26+ struct Completions < ' db > {
27+ db : & ' db dyn Db ,
28+ items : Vec < Completion < ' db > > ,
29+ query : QueryPattern ,
30+ }
31+
32+ impl < ' db > Completions < ' db > {
33+ /// Create a new empty collection of completions.
34+ ///
35+ /// The given typed text should correspond to what we believe
36+ /// the user has typed as part of the next symbol they are writing.
37+ /// This collection will treat it as a query when present, and only
38+ /// add completions that match it.
39+ fn new ( db : & ' db dyn Db , typed : Option < & str > ) -> Completions < ' db > {
40+ let query = typed
41+ . map ( QueryPattern :: new)
42+ . unwrap_or_else ( QueryPattern :: matches_all_symbols) ;
43+ Completions {
44+ db,
45+ items : vec ! [ ] ,
46+ query,
47+ }
48+ }
49+
50+ /// Convert this collection into a simple
51+ /// sequence of completions.
52+ fn into_completions ( mut self ) -> Vec < Completion < ' db > > {
53+ self . items . sort_by ( compare_suggestions) ;
54+ self . items
55+ . dedup_by ( |c1, c2| ( & c1. name , c1. module_name ) == ( & c2. name , c2. module_name ) ) ;
56+ self . items
57+ }
58+
59+ /// Attempts to adds the given completion to this collection.
60+ ///
61+ /// When added, `true` is returned.
62+ ///
63+ /// This might not add the completion for a variety of reasons.
64+ /// For example, if the symbol name does not match this collection's
65+ /// query.
66+ fn try_add ( & mut self , completion : Completion < ' db > ) -> bool {
67+ if !self . query . is_match_symbol_name ( completion. name . as_str ( ) ) {
68+ return false ;
69+ }
70+ self . force_add ( completion) ;
71+ true
72+ }
73+
74+ /// Attempts to adds the given semantic completion to this collection.
75+ ///
76+ /// When added, `true` is returned.
77+ fn try_add_semantic ( & mut self , completion : SemanticCompletion < ' db > ) -> bool {
78+ self . try_add ( Completion :: from_semantic_completion ( self . db , completion) )
79+ }
80+
81+ /// Always adds the given completion to this collection.
82+ fn force_add ( & mut self , completion : Completion < ' db > ) {
83+ self . items . push ( completion) ;
84+ }
85+ }
86+
87+ impl < ' db > Extend < SemanticCompletion < ' db > > for Completions < ' db > {
88+ fn extend < T > ( & mut self , it : T )
89+ where
90+ T : IntoIterator < Item = SemanticCompletion < ' db > > ,
91+ {
92+ for c in it {
93+ self . try_add_semantic ( c) ;
94+ }
95+ }
96+ }
97+
98+ impl < ' db > Extend < Completion < ' db > > for Completions < ' db > {
99+ fn extend < T > ( & mut self , it : T )
100+ where
101+ T : IntoIterator < Item = Completion < ' db > > ,
102+ {
103+ for c in it {
104+ self . try_add ( c) ;
105+ }
106+ }
107+ }
108+
24109#[ derive( Clone , Debug ) ]
25110pub struct Completion < ' db > {
26111 /// The label shown to the user for this suggestion.
@@ -251,11 +336,6 @@ pub fn completion<'db>(
251336 return vec ! [ completions] ;
252337 }
253338
254- let typed_query = typed
255- . as_deref ( )
256- . map ( QueryPattern :: new)
257- . unwrap_or_else ( QueryPattern :: matches_all_symbols) ;
258-
259339 let Some ( target_token) = CompletionTargetTokens :: find ( & parsed, offset) else {
260340 return vec ! [ ] ;
261341 } ;
@@ -282,14 +362,12 @@ pub fn completion<'db>(
282362 ( model. scoped_completions ( scoped. node ) , Some ( scoped) )
283363 }
284364 } ;
285- let mut completions: Vec < Completion < ' _ > > = semantic_completions
286- . into_iter ( )
287- . filter ( |c| typed_query. is_match_symbol_name ( c. name . as_str ( ) ) )
288- . map ( |c| Completion :: from_semantic_completion ( db, c) )
289- . collect ( ) ;
365+
366+ let mut completions = Completions :: new ( db, typed. as_deref ( ) ) ;
367+ completions. extend ( semantic_completions) ;
290368
291369 if scoped. is_some ( ) {
292- add_keyword_completions ( db, & typed_query , & mut completions) ;
370+ add_keyword_completions ( db, & mut completions) ;
293371 }
294372 if settings. auto_import {
295373 if let Some ( scoped) = scoped {
@@ -303,31 +381,22 @@ pub fn completion<'db>(
303381 ) ;
304382 }
305383 }
306- completions. sort_by ( compare_suggestions) ;
307- completions. dedup_by ( |c1, c2| ( & c1. name , c1. module_name ) == ( & c2. name , c2. module_name ) ) ;
308- completions
384+ completions. into_completions ( )
309385}
310386
311387/// Adds completions derived from keywords.
312388///
313389/// This should generally only be used when offering "scoped" completions.
314390/// This will include keywords corresponding to Python values (like `None`)
315391/// and general language keywords (like `raise`).
316- fn add_keyword_completions < ' db > (
317- db : & ' db dyn Db ,
318- query : & QueryPattern ,
319- completions : & mut Vec < Completion < ' db > > ,
320- ) {
392+ fn add_keyword_completions < ' db > ( db : & ' db dyn Db , completions : & mut Completions < ' db > ) {
321393 let keyword_values = [
322394 ( "None" , Type :: none ( db) ) ,
323395 ( "True" , Type :: BooleanLiteral ( true ) ) ,
324396 ( "False" , Type :: BooleanLiteral ( false ) ) ,
325397 ] ;
326398 for ( name, ty) in keyword_values {
327- if !query. is_match_symbol_name ( name) {
328- continue ;
329- }
330- completions. push ( Completion :: value_keyword ( name, ty) ) ;
399+ completions. try_add ( Completion :: value_keyword ( name, ty) ) ;
331400 }
332401
333402 // Note that we specifically omit the `type` keyword here, since
@@ -343,10 +412,7 @@ fn add_keyword_completions<'db>(
343412 "yield" , "case" , "match" ,
344413 ] ;
345414 for name in keywords {
346- if !query. is_match_symbol_name ( name) {
347- continue ;
348- }
349- completions. push ( Completion :: keyword ( name) ) ;
415+ completions. try_add ( Completion :: keyword ( name) ) ;
350416 }
351417}
352418
@@ -377,17 +443,22 @@ fn add_unimported_completions<'db>(
377443 parsed : & ParsedModuleRef ,
378444 scoped : ScopedTarget < ' _ > ,
379445 typed : Option < & str > ,
380- completions : & mut Vec < Completion < ' db > > ,
446+ completions : & mut Completions < ' db > ,
381447) {
382- let Some ( typed) = typed else {
448+ // This is redundant since `all_symbols` will also bail
449+ // when the query can match everything. But we bail here
450+ // to avoid building an `Importer` and other plausibly
451+ // costly work when we know we won't use it.
452+ if completions. query . will_match_everything ( ) {
383453 return ;
384- } ;
454+ }
455+
385456 let source = source_text ( db, file) ;
386457 let stylist = Stylist :: from_tokens ( parsed. tokens ( ) , source. as_str ( ) ) ;
387458 let importer = Importer :: new ( db, & stylist, file, source. as_str ( ) , parsed) ;
388459 let members = importer. members_in_scope_at ( scoped. node , scoped. node . start ( ) ) ;
389460
390- for symbol in all_symbols ( db, typed ) {
461+ for symbol in all_symbols ( db, & completions . query ) {
391462 if symbol. module . file ( db) == Some ( file) {
392463 continue ;
393464 }
@@ -399,7 +470,7 @@ fn add_unimported_completions<'db>(
399470 // "fine," but it might mean that we import a symbol from the
400471 // "wrong" module.
401472 let import_action = importer. import ( request, & members) ;
402- completions. push ( Completion {
473+ completions. add ( Completion {
403474 name : ast:: name:: Name :: new ( & symbol. symbol . name ) ,
404475 insert : Some ( import_action. symbol_text ( ) . into ( ) ) ,
405476 ty : None ,
@@ -975,6 +1046,8 @@ fn is_import_alias_incomplete(tokens: &[Token], typed: Option<&str>) -> bool {
9751046///
9761047/// If there isn't any typed text or it could not otherwise be found,
9771048/// then `None` is returned.
1049+ ///
1050+ /// When `Some` is returned, the string is guaranteed to be non-empty.
9781051fn find_typed_text (
9791052 db : & dyn Db ,
9801053 file : File ,
@@ -997,7 +1070,7 @@ fn find_typed_text(
9971070 // been typed. This likely means there is whitespace
9981071 // or something that isn't represented in the token
9991072 // stream. So just give up.
1000- if last. end ( ) < offset {
1073+ if last. end ( ) < offset || last . range ( ) . is_empty ( ) {
10011074 return None ;
10021075 }
10031076 Some ( source[ last. range ( ) ] . to_string ( ) )
0 commit comments