package com.intellij.lang.javascript.flex;
import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.HyperlinkInfo;
import com.intellij.flex.FlexCommonUtils;
import com.intellij.ide.util.DefaultPsiElementCellRenderer;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
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.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBList;
import com.intellij.util.PathUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class FlexStackTraceFilter implements Filter {
public static final String GLOBAL_PREFIX = "global/";
private final Project myProject;
private static final String AT = "at ";
public FlexStackTraceFilter(Project project) {
myProject = project;
}
@Nullable
public Result applyFilter(final String line, final int entireLength) {
// [trace] at org.flexunit::Assert$/fail()[E:\hudson\jobs\FlexUnit4-Flex4.1\workspace\FlexUnit4\src\org\flexunit\Assert.as:294]
// at org.flexunit::Assert$/fail()[E:\hudson\jobs\FlexUnit4-Flex4.1\workspace\FlexUnit4\src\org\flexunit\Assert.as:294]
// at global/org.flexunit.asserts::fail()[E:\hudson\jobs\FlexUnit4-Flex4.1\workspace\FlexUnit4\src\org\flexunit\asserts\fail.as:39]
// at foo::SomeTest/testSmth()[/untitled5/app/testSrc/foo/SomeTest.as:19]
// at Function/http://adobe.com/AS3/2006/builtin::apply()
// at mx.core::UIComponent/set initialized()[C:\autobuild\3.3.0\frameworks\projects\framework\src\mx\core\UIComponent.as:1169]
final String trimmed = StringUtil.trimStart(line, "[trace]").trim();
final int bracketOpenIndex = line.lastIndexOf('[');
if (!trimmed.startsWith(AT) || bracketOpenIndex < 0 || !trimmed.endsWith("]")) return null;
final int bracketCloseIndex = line.lastIndexOf(']');
final String pathAndLineNumber = FileUtil.toSystemIndependentName(line.substring(bracketOpenIndex + 1, bracketCloseIndex));
String filePath = pathAndLineNumber; // "E:\hudson\jobs\FlexUnit4-Flex4.1\workspace\FlexUnit4\src\org\flexunit\Assert.as:294"
int lineNumber = -1;
final int colonIndex = pathAndLineNumber.lastIndexOf(':');
if (colonIndex > 0) {
try {
lineNumber = Integer.parseInt(pathAndLineNumber.substring(colonIndex + 1)) - 1; // 294 - 1 = 293
filePath =
pathAndLineNumber.substring(0, colonIndex); // "E:\hudson\jobs\FlexUnit4-Flex4.1\workspace\FlexUnit4\src\org\flexunit\Assert.as"
}
catch (NumberFormatException ignore) {/*unlucky*/}
}
if (filePath.startsWith("//")) return null; // UNC path
final String fileName = PathUtil.getFileName(filePath); // Assert.as
if (!FlexCommonUtils.isSourceFile(fileName)) return null; // Flex filter cares only about *.as and *.mxml
final int atIndex = line.indexOf(AT);
String fqnInfo = line.substring(atIndex + AT.length(), bracketOpenIndex).trim(); // "org.flexunit::Assert$/fail()"
fqnInfo = StringUtil.trimStart(fqnInfo, GLOBAL_PREFIX); // for case like "global/org.flexunit.asserts::fail()"
final int slashOrParenIndex = StringUtil.indexOfAny(fqnInfo, "/(");
if (slashOrParenIndex > 0) {
String somethingLikeFqn = fqnInfo.substring(0, slashOrParenIndex); // "org.flexunit::Assert$"
somethingLikeFqn = StringUtil.trimEnd(somethingLikeFqn, "$");
final int dotIndex = fileName.lastIndexOf('.');
final String dotExtension = dotIndex < 0 ? "" : fileName.substring(dotIndex);
final String fileNameWithoutExtension = FileUtil.getNameWithoutExtension(fileName);
if (somethingLikeFqn.equals(fileNameWithoutExtension) || somethingLikeFqn.endsWith("::" + fileNameWithoutExtension)) {
final String relativePath =
StringUtil.replace(somethingLikeFqn, "::", "/").replace('.', '/') + dotExtension; // "org/flexunit/Assert.as"
if (filePath.endsWith(relativePath)) {
final int textStartOffset = entireLength - line.length();
final int highlightEndOffset = textStartOffset + bracketCloseIndex;
return applyFlexStackTraceFilter(filePath, relativePath, lineNumber, highlightEndOffset);
}
}
}
return null;
}
@Nullable
private Result applyFlexStackTraceFilter(final String filePath,
final String matchingRelativePath,
final int lineNumber,
final int highlightEndOffset) {
final Collection<VirtualFile> result = new ArrayList<>();
final Collection<VirtualFile> files =
FilenameIndex.getVirtualFilesByName(myProject, PathUtil.getFileName(filePath), GlobalSearchScope.allScope(myProject));
for (final VirtualFile file : files) {
if (file.getPath().endsWith(matchingRelativePath)) {
result.add(file);
}
}
if (!result.isEmpty()) {
final int colonLineNumberLength = lineNumber > 0 ? (":".length() + String.valueOf(lineNumber).length()) : 0;
int highlightingLength = matchingRelativePath.length() + colonLineNumberLength;
if (result.size() == 1 && result.iterator().next().getPath().equals(filePath)) {
highlightingLength = filePath.length() + colonLineNumberLength;
}
final int highlightStartOffset = highlightEndOffset - highlightingLength;
return new Result(highlightStartOffset, highlightEndOffset, new OpenOneOfSeveralFilesHyperlinkInfo(result, lineNumber));
}
return null;
}
private static class OpenOneOfSeveralFilesHyperlinkInfo implements HyperlinkInfo {
private final Collection<VirtualFile> myFiles;
private final int myLine;
public OpenOneOfSeveralFilesHyperlinkInfo(@NotNull final Collection<VirtualFile> files, final int line) {
myFiles = files;
myLine = line;
}
public void navigate(final Project project) {
final List<VirtualFile> validFiles = new ArrayList<>(myFiles.size());
for (final VirtualFile file : myFiles) {
if (file.isValid()) {
validFiles.add(file);
}
}
if (validFiles.isEmpty()) {
return;
}
if (validFiles.size() == 1) {
FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, validFiles.get(0), myLine, 0), true);
}
else {
final PsiManager psiManager = PsiManager.getInstance(project);
final Collection<PsiFile> psiFiles = new ArrayList<>(validFiles.size());
for (final VirtualFile file : validFiles) {
final PsiFile psiFile = psiManager.findFile(file);
if (psiFile != null) {
psiFiles.add(psiFile);
}
}
final JList list = new JBList(PsiUtilCore.toPsiFileArray(psiFiles));
list.setCellRenderer(new DefaultPsiElementCellRenderer());
final PopupChooserBuilder builder = new PopupChooserBuilder(list);
final JBPopup popup = builder
.setItemChoosenCallback(() -> {
final Object[] selectedElements = list.getSelectedValues();
if (selectedElements != null && selectedElements.length == 1 && selectedElements[0] instanceof PsiFile) {
final VirtualFile file = ((PsiFile)selectedElements[0]).getVirtualFile();
if (file != null) {
FileEditorManager.getInstance(project)
.openTextEditor(new OpenFileDescriptor(project, file, myLine, 0), true);
}
}
})
.createPopup();
final JFrame frame = WindowManager.getInstance().getFrame(project);
final Point mousePosition = frame.getMousePosition();
if (mousePosition != null) {
popup.show(new RelativePoint(frame, mousePosition));
}
}
}
}
}