Skip to content

Commit bc3ad4e

Browse files
committed
Refactor include file selection with performance improvements
Introduced asynchronous file search with `ProgressManager` and wrapped file selection details into a new `FileItem` class, minimizing EDT overhead. Improved user feedback by adding hints for no results and optimized popup rendering by precomputing file paths. Enhanced maintainability and performance of include file resolution logic.
1 parent 3519cb6 commit bc3ad4e

File tree

1 file changed

+109
-52
lines changed

1 file changed

+109
-52
lines changed

src/org/intellij/erlang/quickfixes/ErlangFindIncludeQuickFix.java

Lines changed: 109 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,61 @@
1616

1717
package org.intellij.erlang.quickfixes;
1818

19+
import com.intellij.codeInsight.hint.HintManager;
1920
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
2021
import com.intellij.codeInspection.ProblemDescriptor;
2122
import com.intellij.openapi.application.ApplicationManager;
2223
import com.intellij.openapi.command.CommandProcessor;
2324
import com.intellij.openapi.editor.Editor;
2425
import com.intellij.openapi.module.Module;
2526
import 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;
2630
import com.intellij.openapi.project.Project;
2731
import com.intellij.openapi.project.ProjectUtil;
2832
import com.intellij.openapi.ui.popup.*;
33+
import com.intellij.openapi.util.Computable;
2934
import com.intellij.openapi.util.text.StringUtil;
3035
import com.intellij.openapi.vfs.VfsUtilCore;
3136
import com.intellij.openapi.vfs.VirtualFile;
37+
import com.intellij.psi.PsiAnchor;
3238
import com.intellij.psi.PsiElement;
3339
import com.intellij.psi.PsiFile;
34-
import com.intellij.psi.search.FilenameIndex;
3540
import com.intellij.psi.search.GlobalSearchScope;
3641
import com.intellij.psi.util.PsiEditorUtil;
3742
import com.intellij.util.FileContentUtilCore;
3843
import org.intellij.erlang.icons.ErlangIcons;
44+
import org.intellij.erlang.index.ErlangModuleIndex;
45+
import org.intellij.erlang.psi.ErlangFile;
3946
import org.intellij.erlang.psi.impl.ErlangElementFactory;
4047
import org.intellij.erlang.roots.ErlangIncludeDirectoryUtil;
4148
import org.jetbrains.annotations.NotNull;
4249
import org.jetbrains.annotations.Nullable;
4350

4451
import javax.swing.*;
45-
import java.util.Arrays;
4652
import java.util.Collections;
4753
import java.util.List;
4854

4955
/**
5056
* @author mark-dev
5157
*/
5258
public 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

Comments
 (0)