55 * LICENSE file in the root directory of this source tree.
66 */
77
8+ use dupe:: Dupe ;
89use fuzzy_matcher:: FuzzyMatcher ;
910use fuzzy_matcher:: skim:: SkimMatcherV2 ;
1011use lsp_types:: CompletionItem ;
1112use lsp_types:: CompletionItemKind ;
13+ use lsp_types:: CompletionItemLabelDetails ;
1214use lsp_types:: CompletionItemTag ;
15+ use lsp_types:: TextEdit ;
1316use pyrefly_build:: handle:: Handle ;
1417use pyrefly_python:: docstring:: Docstring ;
1518use pyrefly_python:: dunder;
@@ -19,13 +22,18 @@ use pyrefly_types::literal::Lit;
1922use pyrefly_types:: types:: Union ;
2023use ruff_python_ast:: Identifier ;
2124use ruff_text_size:: Ranged ;
25+ use ruff_text_size:: TextRange ;
2226use ruff_text_size:: TextSize ;
2327
2428use crate :: alt:: attr:: AttrInfo ;
2529use crate :: binding:: binding:: Key ;
2630use crate :: export:: exports:: Export ;
2731use crate :: export:: exports:: ExportLocation ;
32+ use crate :: state:: ide:: import_regular_import_edit;
33+ use crate :: state:: ide:: insert_import_edit;
2834use crate :: state:: lsp:: FindPreference ;
35+ use crate :: state:: lsp:: ImportFormat ;
36+ use crate :: state:: lsp:: MIN_CHARACTERS_TYPED_AUTOIMPORT ;
2937use crate :: state:: state:: Transaction ;
3038use crate :: types:: callable:: Param ;
3139use crate :: types:: types:: Type ;
@@ -316,4 +324,111 @@ impl Transaction<'_> {
316324 ) ;
317325 }
318326 }
327+
328+ /// Adds auto-import completions from exports of other modules using fuzzy matching.
329+ pub ( crate ) fn add_autoimport_completions (
330+ & self ,
331+ handle : & Handle ,
332+ identifier : & Identifier ,
333+ completions : & mut Vec < CompletionItem > ,
334+ import_format : ImportFormat ,
335+ supports_completion_item_details : bool ,
336+ ) {
337+ // Auto-import can be slow. Let's only return results if there are no local
338+ // results for now. TODO: re-enable it once we no longer have perf issues.
339+ // We should not try to generate autoimport when the user has typed very few
340+ // characters. It's unhelpful to narrow down suggestions.
341+ if identifier. as_str ( ) . len ( ) >= MIN_CHARACTERS_TYPED_AUTOIMPORT
342+ && let Some ( ast) = self . get_ast ( handle)
343+ && let Some ( module_info) = self . get_module_info ( handle)
344+ {
345+ for ( handle_to_import_from, name, export) in
346+ self . search_exports_fuzzy ( identifier. as_str ( ) )
347+ {
348+ // Using handle itself doesn't always work because handles can be made separately and have different hashes
349+ if handle_to_import_from. module ( ) == handle. module ( )
350+ || handle_to_import_from. module ( ) == ModuleName :: builtins ( )
351+ {
352+ continue ;
353+ }
354+ let depth = handle_to_import_from. module ( ) . components ( ) . len ( ) ;
355+ let module_description = handle_to_import_from. module ( ) . as_str ( ) . to_owned ( ) ;
356+ let ( insert_text, additional_text_edits, imported_module) = {
357+ let ( position, insert_text, module_name) = insert_import_edit (
358+ & ast,
359+ self . config_finder ( ) ,
360+ handle. dupe ( ) ,
361+ handle_to_import_from,
362+ & name,
363+ import_format,
364+ ) ;
365+ let import_text_edit = TextEdit {
366+ range : module_info. to_lsp_range ( TextRange :: at ( position, TextSize :: new ( 0 ) ) ) ,
367+ new_text : insert_text. clone ( ) ,
368+ } ;
369+ ( insert_text, Some ( vec ! [ import_text_edit] ) , module_name)
370+ } ;
371+ let auto_import_label_detail = format ! ( " (import {imported_module})" ) ;
372+
373+ completions. push ( CompletionItem {
374+ label : name,
375+ detail : Some ( insert_text) ,
376+ kind : export
377+ . symbol_kind
378+ . map_or ( Some ( CompletionItemKind :: VARIABLE ) , |k| {
379+ Some ( k. to_lsp_completion_item_kind ( ) )
380+ } ) ,
381+ additional_text_edits,
382+ label_details : supports_completion_item_details. then_some (
383+ CompletionItemLabelDetails {
384+ detail : Some ( auto_import_label_detail) ,
385+ description : Some ( module_description) ,
386+ } ,
387+ ) ,
388+ tags : if export. deprecation . is_some ( ) {
389+ Some ( vec ! [ CompletionItemTag :: DEPRECATED ] )
390+ } else {
391+ None
392+ } ,
393+ sort_text : Some ( format ! ( "4{}" , depth) ) ,
394+ ..Default :: default ( )
395+ } ) ;
396+ }
397+
398+ for module_name in self . search_modules_fuzzy ( identifier. as_str ( ) ) {
399+ if module_name == handle. module ( ) {
400+ continue ;
401+ }
402+ let module_name_str = module_name. as_str ( ) . to_owned ( ) ;
403+ if let Some ( module_handle) = self . import_handle ( handle, module_name, None ) . finding ( )
404+ {
405+ let ( insert_text, additional_text_edits) = {
406+ let ( position, insert_text) =
407+ import_regular_import_edit ( & ast, module_handle) ;
408+ let import_text_edit = TextEdit {
409+ range : module_info
410+ . to_lsp_range ( TextRange :: at ( position, TextSize :: new ( 0 ) ) ) ,
411+ new_text : insert_text. clone ( ) ,
412+ } ;
413+ ( insert_text, Some ( vec ! [ import_text_edit] ) )
414+ } ;
415+ let auto_import_label_detail = format ! ( " (import {module_name_str})" ) ;
416+
417+ completions. push ( CompletionItem {
418+ label : module_name_str. clone ( ) ,
419+ detail : Some ( insert_text) ,
420+ kind : Some ( CompletionItemKind :: MODULE ) ,
421+ additional_text_edits,
422+ label_details : supports_completion_item_details. then_some (
423+ CompletionItemLabelDetails {
424+ detail : Some ( auto_import_label_detail) ,
425+ description : Some ( module_name_str) ,
426+ } ,
427+ ) ,
428+ ..Default :: default ( )
429+ } ) ;
430+ }
431+ }
432+ }
433+ }
319434}
0 commit comments