1616
1717package org .intellij .erlang .quickfixes ;
1818
19+ import com .intellij .codeInsight .hint .HintManager ;
1920import com .intellij .codeInsight .intention .preview .IntentionPreviewInfo ;
2021import com .intellij .codeInspection .ProblemDescriptor ;
2122import com .intellij .openapi .application .ApplicationManager ;
2223import com .intellij .openapi .command .CommandProcessor ;
2324import com .intellij .openapi .editor .Editor ;
2425import com .intellij .openapi .module .Module ;
2526import com .intellij .openapi .module .ModuleUtilCore ;
27+ import com .intellij .openapi .progress .ProgressIndicator ;
28+ import com .intellij .openapi .progress .ProgressManager ;
29+ import com .intellij .openapi .progress .Task ;
2630import com .intellij .openapi .project .Project ;
2731import com .intellij .openapi .project .ProjectUtil ;
2832import com .intellij .openapi .ui .popup .*;
33+ import com .intellij .openapi .util .Computable ;
2934import com .intellij .openapi .util .text .StringUtil ;
3035import com .intellij .openapi .vfs .VfsUtilCore ;
3136import com .intellij .openapi .vfs .VirtualFile ;
37+ import com .intellij .psi .PsiAnchor ;
3238import com .intellij .psi .PsiElement ;
3339import com .intellij .psi .PsiFile ;
34- import com .intellij .psi .search .FilenameIndex ;
3540import com .intellij .psi .search .GlobalSearchScope ;
3641import com .intellij .psi .util .PsiEditorUtil ;
3742import com .intellij .util .FileContentUtilCore ;
3843import org .intellij .erlang .icons .ErlangIcons ;
44+ import org .intellij .erlang .index .ErlangModuleIndex ;
45+ import org .intellij .erlang .psi .ErlangFile ;
3946import org .intellij .erlang .psi .impl .ErlangElementFactory ;
4047import org .intellij .erlang .roots .ErlangIncludeDirectoryUtil ;
4148import org .jetbrains .annotations .NotNull ;
4249import org .jetbrains .annotations .Nullable ;
4350
4451import javax .swing .*;
45- import java .util .Arrays ;
4652import java .util .Collections ;
4753import java .util .List ;
4854
4955/**
5056 * @author mark-dev
5157 */
5258public class ErlangFindIncludeQuickFix extends ErlangQuickFixBase {
59+
60+ /**
61+ * Data class that holds a PsiAnchor for a file and its pre-calculated display path.
62+ * This avoids expensive calculations during UI rendering and prevents EDT issues.
63+ */
64+ private static class FileItem {
65+ final PsiAnchor fileAnchor ;
66+ final String displayPath ;
67+
68+ FileItem (PsiAnchor fileAnchor , String displayPath ) {
69+ this .fileAnchor = fileAnchor ;
70+ this .displayPath = displayPath ;
71+ }
72+ }
73+
5374 private static final char INCLUDE_STRING_PATH_SEPARATOR = '/' ;
5475 /*
5576 * if true after adding facets include string will be renamed to direct link on hrl file
@@ -74,29 +95,51 @@ public void applyFix(@NotNull Project project,
7495
7596 PsiElement problem = problemDescriptor .getPsiElement ();
7697 if (problem == null ) return ;
98+ Editor editor = PsiEditorUtil .findEditor (problem );
99+ if (editor == null ) return ;
77100
78101 //Looks for a file that is referenced by include string
79102 String includeString = StringUtil .unquoteString (problem .getText ());
80103 String includeFileName = getFileName (includeString );
81- PsiFile [] matchFiles = searchFileInsideProject (project , includeFileName );
82- if (matchFiles .length == 0 ) {
83- return ;
84- }
85-
86- ApplicationManager .getApplication ().assertIsDispatchThread ();
87104
88- //Single file found
89- if (matchFiles .length == 1 ) {
90- CommandProcessor .getInstance ().executeCommand (project , () -> ApplicationManager .getApplication ().runWriteAction (() -> {
91- fixUsingIncludeFile (problem , matchFiles [0 ]);
92- renameIncludeString (project , problem , setDirectHrlLink , includeString , includeFileName );
93- FileContentUtilCore .reparseFiles (Collections .singletonList (problem .getContainingFile ().getVirtualFile ()));
94- }), "Include File" , "Include File" );
95- }
96- //Multiple files -- allow user select which file should be imported
97- if (matchFiles .length > 1 ) {
98- displayPopupListDialog (project , problem , matchFiles , setDirectHrlLink , includeString , includeFileName );
99- }
105+ ProgressManager .getInstance ().run (
106+ new Task .Backgroundable (project , editor .getComponent (), "Searching for include file '" + includeFileName + "'..." , true , null ) {
107+ @ Override
108+ public void run (@ NotNull ProgressIndicator indicator ) {
109+ indicator .setIndeterminate (true );
110+ List <ErlangFile > files = ApplicationManager .getApplication ().runReadAction (
111+ (Computable <List <ErlangFile >>) () -> {
112+ // pass indicator into the search
113+ return ErlangModuleIndex .getFilesByName (project , includeFileName , GlobalSearchScope .allScope (project ));
114+ }
115+ );
116+
117+ // Pre-compute all display paths and create FileItem objects
118+ // This work is done outside the EDT to avoid performance issues
119+ // during popup rendering
120+ final List <FileItem > fileItems = files .stream ().map (file -> {
121+ PsiAnchor anchor = PsiAnchor .create (file );
122+ String displayPath = calcFilePath (file , project );
123+ return new FileItem (anchor , displayPath );
124+ })
125+ .toList ();
126+
127+
128+ ApplicationManager .getApplication ().invokeLater (() -> {
129+ if (!files .isEmpty ()) {
130+ displayPopupListDialog (project , problem , fileItems , setDirectHrlLink , includeString , includeFileName );
131+ }
132+ else {
133+ // Show editor notification that no files are found
134+ Editor editor = PsiEditorUtil .findEditor (problem );
135+ if (editor != null ) {
136+ HintManager .getInstance ().showErrorHint (editor , "No files matching '" + includeFileName + "' were found" );
137+ }
138+ }
139+ });
140+ }
141+ }
142+ );
100143 }
101144
102145 @ Override
@@ -118,47 +161,47 @@ private static void renameIncludeString(Project project,
118161
119162 private static void displayPopupListDialog (final Project project ,
120163 final PsiElement problem ,
121- final PsiFile [] files ,
164+ final List < FileItem > fileItems ,
122165 final boolean setDirectHrlLink ,
123166 final String includeString ,
124167 final String includeFileName
125168 ) {
126169 final Editor problemEditor = PsiEditorUtil .findEditor (problem );
127- if (problemEditor == null ) {
128- return ;
129- }
130- ListPopup p = JBPopupFactory .getInstance ().createListPopup (new ListPopupStep <PsiFile >() {
170+ if (problemEditor == null ) return ;
131171
172+ ListPopup p = JBPopupFactory .getInstance ().createListPopup (new ListPopupStep <FileItem >() {
132173 @ NotNull
133174 @ Override
134- public List <PsiFile > getValues () {
135- return Arrays . asList ( files ) ;
175+ public List <FileItem > getValues () {
176+ return fileItems ;
136177 }
137178
138179 @ Override
139- public boolean isSelectable (PsiFile o ) {
180+ public boolean isSelectable (FileItem item ) {
140181 return true ;
141182 }
142183
143184 @ NotNull
144185 @ Override
145- public Icon getIconFor (PsiFile o ) {
146- return ErlangIcons .HEADER ;
186+ public Icon getIconFor (FileItem item ) {
187+ if (item .displayPath .endsWith ("hrl" )) {
188+ return ErlangIcons .HEADER ;
189+ }
190+ else {
191+ return ErlangIcons .FILE ;
192+ }
147193 }
148194
149195 @ NotNull
150196 @ Override
151- public String getTextFor (PsiFile o ) {
152- // Uses relative path to project root if possible (if not - full path)
153- VirtualFile f = o .getVirtualFile ();
154- VirtualFile projectDir = ProjectUtil .guessProjectDir (project );
155- String projectRootRelativePath = projectDir == null ? null : VfsUtilCore .getRelativePath (f , projectDir , INCLUDE_STRING_PATH_SEPARATOR );
156- return projectRootRelativePath == null ? f .getPath () : projectRootRelativePath ;
197+ public String getTextFor (FileItem item ) {
198+ // Return the pre-calculated display path
199+ return item .displayPath ;
157200 }
158201
159202 @ Nullable
160203 @ Override
161- public ListSeparator getSeparatorAbove (PsiFile o ) {
204+ public ListSeparator getSeparatorAbove (FileItem item ) {
162205 return null ;
163206 }
164207
@@ -170,23 +213,26 @@ public int getDefaultOptionIndex() {
170213 @ NotNull
171214 @ Override
172215 public String getTitle () {
173- return "Multiple Files Found " ;
216+ return StringUtil . pluralize ( "File" ) + " to Include " ;
174217 }
175218
176219 @ Nullable
177220 @ Override
178- public PopupStep <PsiFile > onChosen (PsiFile o , boolean b ) {
221+ public PopupStep <FileItem > onChosen (FileItem item , boolean finalChoice ) {
179222 CommandProcessor .getInstance ().executeCommand (project , () -> ApplicationManager .getApplication ().runWriteAction (() -> {
180- fixUsingIncludeFile (problem , o );
181- renameIncludeString (project , problem , setDirectHrlLink , includeString , includeFileName );
182- FileContentUtilCore .reparseFiles (Collections .singletonList (problem .getContainingFile ().getVirtualFile ()));
183- }), "Add Facet Action (Find Include Quick Fix)" , "Include File" , problemEditor .getDocument ());
223+ PsiElement element = item .fileAnchor .retrieve ();
224+ if (element instanceof PsiFile ) {
225+ fixUsingIncludeFile (problem , (PsiFile ) element );
226+ renameIncludeString (project , problem , setDirectHrlLink , includeString , includeFileName );
227+ FileContentUtilCore .reparseFiles (Collections .singletonList (problem .getContainingFile ().getVirtualFile ()));
228+ }
229+ }), "Include File Fix" , "Include File" , problemEditor .getDocument ());
184230
185231 return null ;
186232 }
187233
188234 @ Override
189- public boolean hasSubstep (PsiFile o ) {
235+ public boolean hasSubstep (FileItem item ) {
190236 return false ;
191237 }
192238
@@ -201,7 +247,7 @@ public boolean isMnemonicsNavigationEnabled() {
201247
202248 @ Nullable
203249 @ Override
204- public MnemonicNavigationFilter <PsiFile > getMnemonicNavigationFilter () {
250+ public MnemonicNavigationFilter <FileItem > getMnemonicNavigationFilter () {
205251 return null ;
206252 }
207253
@@ -212,7 +258,7 @@ public boolean isSpeedSearchEnabled() {
212258
213259 @ Nullable
214260 @ Override
215- public SpeedSearchFilter <PsiFile > getSpeedSearchFilter () {
261+ public SpeedSearchFilter <FileItem > getSpeedSearchFilter () {
216262 return null ;
217263 }
218264
@@ -230,12 +276,28 @@ public Runnable getFinalRunnable() {
230276 p .showInBestPositionFor (problemEditor );
231277 }
232278
279+ private static @ NotNull String calcFilePath (PsiFile file , Project project ) {
280+ if (file == null ) return "<invalid file>" ;
281+
282+ VirtualFile f = file .getVirtualFile ();
283+ if (f == null ) return "<unknown path>" ;
284+
285+ VirtualFile projectDir = ProjectUtil .guessProjectDir (project );
286+ String projectRootRelativePath = projectDir == null ? null : VfsUtilCore .getRelativePath (f , projectDir , INCLUDE_STRING_PATH_SEPARATOR );
287+ return projectRootRelativePath == null ? f .getPath () : projectRootRelativePath ;
288+ }
289+
233290 private static void fixUsingIncludeFile (PsiElement problem ,
234291 PsiFile includeFile ) {
235292 //Search the module that contains the current(problem) file & fix facets
236293 Module containedModule = ModuleUtilCore .findModuleForPsiElement (problem );
237294 if (containedModule == null ) return ;
238- ErlangIncludeDirectoryUtil .markAsIncludeDirectory (containedModule , includeFile .getVirtualFile ().getParent ());
295+
296+ VirtualFile includeFileParent = includeFile .getVirtualFile () != null ?
297+ includeFile .getVirtualFile ().getParent () : null ;
298+ if (includeFileParent != null ) {
299+ ErlangIncludeDirectoryUtil .markAsIncludeDirectory (containedModule , includeFileParent );
300+ }
239301 }
240302
241303 /*
@@ -250,9 +312,4 @@ private static String getFileName(String includeString) {
250312 int index = includeString .lastIndexOf (INCLUDE_STRING_PATH_SEPARATOR );
251313 return includeString .substring (index + 1 );
252314 }
253-
254- private static PsiFile [] searchFileInsideProject (Project project , String fileName ) {
255- return FilenameIndex .getFilesByName (project , fileName , GlobalSearchScope .allScope (project ));
256- }
257-
258315}
0 commit comments