package com.haskforce.actions;
import com.haskforce.HaskellIcons;
import com.haskforce.utils.FileUtil;
import com.intellij.ide.actions.CreateFileAction;
import com.intellij.ide.actions.CreateFileFromTemplateAction;
import com.intellij.ide.actions.CreateFileFromTemplateDialog;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.FileTemplateUtil;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.InputValidatorEx;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.ParseException;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;
/**
* The "New Haskell file" menu item+actions available under "New".
*/
public class CreateHaskellFileAction extends CreateFileFromTemplateAction implements DumbAware {
private static final String NEW_HASKELL_FILE = "New Haskell File";
private static final Pattern VALID_MODULE_NAME_REGEX = Pattern.compile("^([A-Z][A-Za-z0-9]*)(\\.[A-Z][A-Za-z0-9]*)*(.hs)?$");
public CreateHaskellFileAction() {
super(NEW_HASKELL_FILE, "", HaskellIcons.FILE);
}
@Override
protected void buildDialog(Project project, PsiDirectory directory, CreateFileFromTemplateDialog.Builder builder) {
builder.setTitle(NEW_HASKELL_FILE)
.addKind("Empty module", HaskellIcons.FILE, "Haskell Module")
.setValidator(new InputValidatorEx() {
@Nullable
@Override
public String getErrorText(String inputString) {
final String error = " is not a valid Haskell module name.";
if (inputString.isEmpty()) {
return null;
}
if (VALID_MODULE_NAME_REGEX.matcher(inputString).matches()) {
return null;
}
return '\'' + inputString + '\'' + error;
}
@Override
public boolean checkInput(String inputString) {
return true;
}
@Override
public boolean canClose(String inputString) {
return getErrorText(inputString) == null;
}
});
}
@Override
protected PsiFile createFileFromTemplate(@NotNull String name, @NotNull FileTemplate template, @NotNull PsiDirectory dir) {
// Strip extension so we don't end up with a file saved as "Foo.hs.hs" and content of `module Foo.hs where`
if (name.endsWith(".hs")) {
name = name.substring(0, name.lastIndexOf('.'));
}
List<String> pathParts = StringUtil.split(name, ".");
// Create any intermediate subdirectories.
PsiDirectory subDir = dir;
for (int i = 0; i < pathParts.size() - 1; ++i) {
subDir = FileUtil.findOrCreateSubdirectory(subDir, pathParts.get(i));
}
String moduleName = pathParts.get(pathParts.size() - 1);
return createFileFromTemplate(moduleName, template, subDir, getDefaultTemplateProperty());
}
@SuppressWarnings("DialogTitleCapitalization")
@Nullable
public static PsiFile createFileFromTemplate(@SuppressWarnings("NullableProblems") @NotNull String name,
@NotNull FileTemplate template,
@NotNull PsiDirectory dir,
@Nullable String defaultTemplateProperty) {
// TODO: Do we *have* to hack the IntelliJ source?
// This is a roughly a copy/paste then slight adaptation from the IntelliJ definition of this method.
// We can't override it directly, and more importantly we can't override its call to
// FileTemplateUtil.createFromTemplate()
List<String> pathItems = FileUtil.getPathFromSourceRoot(dir.getProject(), dir.getVirtualFile());
// modulePrefix is the empty string if the module is either in the top
// level directory or one of the subdirectories start with a lower-case
// letter.
final String modulePrefix = pathItems == null || invalidPathItems(pathItems) ? "" : StringUtil.join(pathItems, ".");
// Adapted from super definition.
CreateFileAction.MkDirs mkdirs = new CreateFileAction.MkDirs(name, dir);
name = mkdirs.newName;
dir = mkdirs.directory;
PsiElement element;
Project project = dir.getProject();
try {
// Patch props with custom property.
Properties props = FileTemplateManager.getInstance(project).getDefaultProperties();
props.setProperty("HASKELL_MODULE_NAME", modulePrefix.isEmpty() ? name : modulePrefix + '.' + name);
element = FileTemplateUtil
.createFromTemplate(template, name, props, dir);
final PsiFile psiFile = element.getContainingFile();
final VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile != null) {
FileEditorManager.getInstance(project).openFile(virtualFile, true);
if (defaultTemplateProperty != null) {
PropertiesComponent.getInstance(project).setValue(defaultTemplateProperty, template.getName());
}
return psiFile;
}
}
catch (ParseException e) {
Messages.showErrorDialog(project, "Error parsing Velocity template: " + e.getMessage(), "Create File from Template");
return null;
}
catch (IncorrectOperationException e) {
throw e;
}
catch (Exception e) {
LOG.error(e);
}
return null;
}
/**
* Returns true if any directory name starts with a lower case letter.
*/
private static boolean invalidPathItems(List<String> pathItems) {
for (String s : pathItems) {
if (s.isEmpty() || !StringUtil.isCapitalized(s.substring(0,1))) return true;
}
return false;
}
@Override
protected String getActionName(PsiDirectory directory, String newName, String templateName) {
return NEW_HASKELL_FILE;
}
}