package com.google.jstestdriver.idea.assertFramework.jstd;
import com.google.jstestdriver.idea.assertFramework.library.JstdLibraryUtil;
import com.google.jstestdriver.idea.debug.JstdDebugProgramRunner;
import com.google.jstestdriver.idea.execution.JstdRunConfigurationProducer;
import com.google.jstestdriver.idea.execution.JstdSettingsUtil;
import com.intellij.codeHighlighting.Pass;
import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.codeInsight.daemon.LineMarkerProvider;
import com.intellij.execution.*;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.execution.actions.ConfigurationFromContext;
import com.intellij.execution.actions.RunConfigurationProducer;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.execution.runners.ExecutionEnvironmentBuilder;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.javascript.testFramework.qunit.QUnitFileStructure;
import com.intellij.javascript.testFramework.qunit.QUnitFileStructureBuilder;
import com.intellij.lang.javascript.psi.JSCallExpression;
import com.intellij.lang.javascript.psi.JSFile;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.ui.ListCellRendererWrapper;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBList;
import com.intellij.util.Function;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.List;
/**
* @author Sergey Simonchik
*/
public class JstdAssertionFrameworkLineMarkerProvider implements LineMarkerProvider {
@Override
public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
Project project = element.getProject();
JSFile jsFile = ObjectUtils.tryCast(element.getContainingFile(), JSFile.class);
if (jsFile == null) {
return null;
}
if (!JstdSettingsUtil.areJstdConfigFilesInProjectCached(element.getProject())) {
return null;
}
LineMarkerInfo lineMarkerInfo = getJstdLineMarkerInfo(project, jsFile, element);
if (lineMarkerInfo == null) {
lineMarkerInfo = getQUnitLineMarkerInfo(jsFile, element);
}
return lineMarkerInfo;
}
@Nullable
private static LineMarkerInfo getJstdLineMarkerInfo(@NotNull Project project,
@NotNull JSFile jsFile,
@NotNull PsiElement element) {
VirtualFile virtualFile = jsFile.getVirtualFile();
if (virtualFile == null) {
return null;
}
boolean inScope = JstdLibraryUtil.isFileInJstdLibScope(project, virtualFile);
if (!inScope) {
return null;
}
JstdTestFileStructure fileStructure = JstdTestFileStructureBuilder.getInstance().fetchCachedTestFileStructure(jsFile);
String testElementName = fileStructure.getNameByPsiElement(element);
if (testElementName == null) {
return null;
}
return createLineMarkerFromElement(element, testElementName);
}
@Nullable
private static LineMarkerInfo getQUnitLineMarkerInfo(@NotNull JSFile jsFile,
@NotNull PsiElement element) {
QUnitFileStructure qunitFileStructure = QUnitFileStructureBuilder.getInstance().fetchCachedTestFileStructure(jsFile);
if (element instanceof JSCallExpression) {
JSCallExpression callExpression = (JSCallExpression) element;
String testElementName = qunitFileStructure.getNameByPsiElement(callExpression);
if (testElementName == null) {
return null;
}
return createLineMarkerFromElement(element, testElementName);
}
return null;
}
@Override
public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) {
// does nothing
}
private static LineMarkerInfo createLineMarkerFromElement(@NotNull PsiElement testElement,
@NotNull final String displayName) {
return new LineMarkerInfo<PsiElement>(
testElement,
testElement.getTextRange(),
AllIcons.Vcs.Arrow_right,
Pass.UPDATE_ALL,
new Function<PsiElement, String>() {
@Override
public String fun(PsiElement element) {
return "Execute '" + displayName + "'";
}
},
new GutterIconNavigationHandler<PsiElement>() {
@Override
public void navigate(MouseEvent e, PsiElement elt) {
if (elt.isValid()) {
showPopup(e, elt, displayName);
}
}
},
GutterIconRenderer.Alignment.RIGHT
);
}
private static void showPopup(@NotNull MouseEvent e, @NotNull final PsiElement psiElement, final String displayName) {
final JBList list = new JBList(getAvailableTypes());
list.setCellRenderer(new ListCellRendererWrapper<Type>() {
@Override
public void customize(JList list, Type value, int index, boolean selected, boolean hasFocus) {
setIcon(value.getIcon());
setText(value.getTitle(displayName));
}
});
PopupChooserBuilder builder = new PopupChooserBuilder(list);
JBPopup popup = builder.
setMovable(true).
setItemChoosenCallback(new Runnable() {
@Override
public void run() {
int[] ids = list.getSelectedIndices();
if (ids.length == 0) return;
Type type = ObjectUtils.tryCast(list.getSelectedValue(), Type.class);
if (type != null) {
if (psiElement.isValid()) {
execute(type.getExecutor(), psiElement);
}
}
}
}).
createPopup();
popup.show(new RelativePoint(e));
}
@NotNull
private static Type[] getAvailableTypes() {
List<Type> types = ContainerUtil.filter(Type.values(), new Condition<Type>() {
@Override
public boolean value(Type type) {
return type.isAvailable();
}
});
return types.toArray(new Type[types.size()]);
}
private static void execute(@NotNull Executor executor, @NotNull final PsiElement element) {
Project project = element.getProject();
Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
if (editor == null) {
return;
}
DataContext dataContext = createDataContext(editor, element);
RunConfigurationProducer jstdOriginalProducer = getJstdRunConfigurationProducer();
if (jstdOriginalProducer == null) {
return;
}
ConfigurationContext context = ConfigurationContext.getFromContext(dataContext);
boolean created = false;
RunnerAndConfigurationSettings configuration = jstdOriginalProducer.findExistingConfiguration(context);
if (configuration == null) {
created = true;
ConfigurationFromContext fromContext = jstdOriginalProducer.createConfigurationFromContext(context);
if (fromContext != null) {
configuration = fromContext.getConfigurationSettings();
}
else {
return;
}
}
execute(project, executor, configuration, created);
}
private static DataContext createDataContext(@NotNull Editor editor, @NotNull final PsiElement element) {
final DataContext dataContext = DataManager.getInstance().getDataContext(editor.getComponent());
return new DataContext() {
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (Location.DATA_KEY.is(dataId)) {
return new PsiLocation<PsiElement>(element.getProject(), element);
}
return dataContext.getData(dataId);
}
};
}
@Nullable
private static RunConfigurationProducer getJstdRunConfigurationProducer() {
return RunConfigurationProducer.getInstance(JstdRunConfigurationProducer.class);
}
private static void execute(@NotNull Project project,
@NotNull Executor executor,
@NotNull RunnerAndConfigurationSettings configuration,
boolean created) {
RunManagerEx runManager = RunManagerEx.getInstanceEx(project);
if (created) {
runManager.setTemporaryConfiguration(configuration);
}
runManager.setSelectedConfiguration(configuration);
ExecutionEnvironmentBuilder builder = ExecutionEnvironmentBuilder.createOrNull(executor, configuration);
if (builder != null) {
ExecutionManager.getInstance(project).restartRunProfile(builder.build());
}
}
private enum Type {
RUN {
@Override
boolean isAvailable() {
return true;
}
@NotNull
@Override
Icon getIcon() {
return AllIcons.Toolwindows.ToolWindowRun;
}
@Override
String getTitle(@NotNull String displayName) {
return "Run '" + displayName + "'";
}
@NotNull
@Override
Executor getExecutor() {
return DefaultRunExecutor.getRunExecutorInstance();
}
},
DEBUG {
@Override
boolean isAvailable() {
return JstdDebugProgramRunner.isAvailable();
}
@NotNull
@Override
Icon getIcon() {
return AllIcons.Toolwindows.ToolWindowDebugger;
}
@Override
String getTitle(@NotNull String displayName) {
return "Debug '" + displayName + "'";
}
@NotNull
@Override
Executor getExecutor() {
return DefaultDebugExecutor.getDebugExecutorInstance();
}
};
abstract boolean isAvailable();
@NotNull
abstract Icon getIcon();
abstract String getTitle(@NotNull String displayName);
@NotNull
abstract Executor getExecutor();
}
}