/*
* Copyright 2012-2014 Sergey Ignatov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.intellij.erlang.quickfixes;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.util.FileContentUtilCore;
import org.intellij.erlang.icons.ErlangIcons;
import org.intellij.erlang.psi.impl.ErlangElementFactory;
import org.intellij.erlang.roots.ErlangIncludeDirectoryUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Arrays;
import java.util.List;
/**
* @author mark-dev
*/
public class ErlangFindIncludeQuickFix extends ErlangQuickFixBase {
private static char INCLUDE_STRING_PATH_SEPARATOR = '/';
/*
* if true after adding facets include string will be renamed to direct link on hrl file
* eg: -include("pr285_helper/include/internal_communication.hrl")
* will be renamed to -include("internal_communication.hrl").
*/
private boolean setDirectHrlLink;
public ErlangFindIncludeQuickFix(boolean setDirectHrlLink) {
this.setDirectHrlLink = setDirectHrlLink;
}
@NotNull
@Override
public String getFamilyName() {
return "Find include";
}
public void applyFix(@NotNull Project project,
@NotNull ProblemDescriptor problemDescriptor) {
PsiElement problem = problemDescriptor.getPsiElement();
if (problem == null) return;
//Looks for a file that is referenced by include string
String includeString = StringUtil.unquoteString(problem.getText());
String includeFileName = getFileName(includeString);
PsiFile[] matchFiles = searchFileInsideProject(project, includeFileName);
if (matchFiles.length == 0) {
return;
}
//Single file found
if (matchFiles.length == 1) {
fixUsingIncludeFile(problem, matchFiles[0]);
renameIncludeString(project, problem, setDirectHrlLink, includeString, includeFileName);
}
//Multiple files -- allow user select which file should be imported
if (matchFiles.length > 1) {
displayPopupListDialog(project, problem, matchFiles, setDirectHrlLink, includeString, includeFileName);
}
}
private static void renameIncludeString(Project project,
PsiElement problem,
boolean setDirectHrlLink,
String includeString,
String includeFileName) {
//Rename include string according setDirectHrlLink
if (setDirectHrlLink && !includeString.equals(includeFileName)) {
problem.replace(ErlangElementFactory.createIncludeString(project, includeFileName));
}
}
private static void displayPopupListDialog(final Project project,
final PsiElement problem,
final PsiFile[] files,
final boolean setDirectHrlLink,
final String includeString,
final String includeFileName
) {
final Editor problemEditor = PsiUtilBase.findEditor(problem);
if (problemEditor == null) {
return;
}
ListPopup p = JBPopupFactory.getInstance().createListPopup(new ListPopupStep() {
@NotNull
@Override
public List getValues() {
return Arrays.asList(files);
}
@Override
public boolean isSelectable(Object o) {
return true;
}
@Nullable
@Override
public Icon getIconFor(Object o) {
return ErlangIcons.HEADER;
}
@NotNull
@Override
public String getTextFor(Object o) {
//Uses relative path to project root if possible (if not - full path)
VirtualFile f = ((PsiFile) o).getVirtualFile();
String projectRootRelativePath = VfsUtilCore.getRelativePath(f, project.getBaseDir(), INCLUDE_STRING_PATH_SEPARATOR);
return projectRootRelativePath == null ?
f.getPath() : projectRootRelativePath;
}
@Nullable
@Override
public ListSeparator getSeparatorAbove(Object o) {
return null;
}
@Override
public int getDefaultOptionIndex() {
return 0;
}
@Nullable
@Override
public String getTitle() {
return "Multiple files found";
}
@Nullable
@Override
public PopupStep onChosen(Object o, boolean b) {
final PsiFile f = (PsiFile) o;
CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
fixUsingIncludeFile(problem, f);
renameIncludeString(project, problem, setDirectHrlLink, includeString, includeFileName);
FileContentUtilCore.reparseFiles(Arrays.asList(problem.getContainingFile().getVirtualFile()));
}), "add facet action(find include quick fix)", null, problemEditor.getDocument());
return null;
}
@Override
public boolean hasSubstep(Object o) {
return false;
}
@Override
public void canceled() {
}
@Override
public boolean isMnemonicsNavigationEnabled() {
return false;
}
@Nullable
@Override
public MnemonicNavigationFilter getMnemonicNavigationFilter() {
return null;
}
@Override
public boolean isSpeedSearchEnabled() {
return false;
}
@Nullable
@Override
public SpeedSearchFilter getSpeedSearchFilter() {
return null;
}
@Override
public boolean isAutoSelectionEnabled() {
return false;
}
@Nullable
@Override
public Runnable getFinalRunnable() {
return null;
}
});
p.showInBestPositionFor(problemEditor);
}
private static void fixUsingIncludeFile(PsiElement problem,
PsiFile includeFile) {
//Search the module that contains the current(problem) file & fix facets
Module containedModule = ModuleUtilCore.findModuleForPsiElement(problem);
if (containedModule == null) return;
ErlangIncludeDirectoryUtil.markAsIncludeDirectory(containedModule, includeFile.getVirtualFile().getParent());
}
/*
* returns file name from includeString
* eg:
* getFileName("pr285_helper/include/internal_communication.hrl")
* -> "internal_communications.hrl"
* getFileName("ecst_events.hrl")
* -> "ecst_events.hrl"
* */
private static String getFileName(String includeString) {
int index = includeString.lastIndexOf(INCLUDE_STRING_PATH_SEPARATOR);
return includeString.substring(index + 1);
}
private static PsiFile[] searchFileInsideProject(Project project, String fileName) {
return FilenameIndex.getFilesByName(project, fileName, GlobalSearchScope.allScope(project));
}
}