package com.google.jstestdriver.idea.execution.tree; import com.google.jstestdriver.idea.config.JstdConfigStructure; import com.intellij.execution.filters.HyperlinkInfo; import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView; import com.intellij.execution.testframework.ui.TestsOutputConsolePrinter; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; class StacktracePrinter extends TestsOutputConsolePrinter { private static final String DEFAULT_PATH_PREFIX = "/test/"; private final JstdConfigStructure myConfigStructure; private final HyperlinkInfoHelper myHyperlinkInfoHelper; public StacktracePrinter(SMTRunnerConsoleView consoleView, JstdConfigStructure configStructure, String browserName) { super(consoleView, consoleView.getProperties(), null); myConfigStructure = configStructure; myHyperlinkInfoHelper = findHyperlinkPrinter(browserName); } @Nullable private static HyperlinkInfoHelper findHyperlinkPrinter(String browserName) { if (browserName == null) { return null; } browserName = browserName.toLowerCase(); if (browserName.startsWith("chrome")) { return ChromeHyperlinkInfoHelper.INSTANCE; } else if (browserName.startsWith("firefox")) { return FirefoxHyperlinkInfoHelper.INSTANCE; } return null; } public void defaultPrint(String text, ConsoleViewContentType contentType) { super.print(text, contentType); } @Override public void print(String text, ConsoleViewContentType contentType) { if (myHyperlinkInfoHelper == null) { defaultPrint(text, contentType); return; } if (contentType == ConsoleViewContentType.ERROR_OUTPUT) { text = text.replace("\r\n", "\n"); StringTokenizer tokenizer = new StringTokenizer(text, "\n", true); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (!writeHyperlinkIfPossible(myHyperlinkInfoHelper, token, contentType)) { defaultPrint(token, contentType); } } } else { defaultPrint(text, contentType); } } private boolean writeHyperlinkIfPossible(HyperlinkInfoHelper hyperlinkInfoHelper, String line, ConsoleViewContentType consoleViewContentType) { Pattern[] urlPatterns = hyperlinkInfoHelper.getAllPossibleUrlPatterns(); for (Pattern urlPattern : urlPatterns) { Matcher matcher = urlPattern.matcher(line); if (matcher.find()) { String text = matcher.group(1); HyperlinkInfo hyperlinkInfo = buildHyperlinkInfo(text, hyperlinkInfoHelper); if (hyperlinkInfo != null) { defaultPrint(line.substring(0, matcher.start(1)), consoleViewContentType); printHyperlink(text, hyperlinkInfo); defaultPrint(line.substring(matcher.end(1)), consoleViewContentType); return true; } } } return false; } private HyperlinkInfo buildHyperlinkInfo(String urlWithPosition, HyperlinkInfoHelper hyperlinkInfoHelper) { String[] components = urlWithPosition.split(":"); if (components.length < 2) { return null; } Integer lastNum = toInteger(components[components.length - 1]); if (lastNum == null) { return null; } int lineNo = lastNum; Integer columnNo = null; String urlStr = StringUtil.join(components, 0, components.length - 1, ":"); if (components.length >= 3) { Integer preLastNum = toInteger(components[components.length - 2]); if (preLastNum != null) { urlStr = StringUtil.join(components, 0, components.length - 2, ":"); lineNo = preLastNum; columnNo = lastNum; } } lineNo = hyperlinkInfoHelper.zeroBaseLineNo(lineNo); if (columnNo != null) { columnNo = hyperlinkInfoHelper.zeroBaseColumnNo(columnNo); } return createHyperlinkInfoFromParts(urlStr, lineNo, columnNo); } private static Integer toInteger(@NotNull String s) { try { return Integer.parseInt(s); } catch (Exception e) { return null; } } /** * Creates HyperlinkInfo instance by structured info. * @param url JS file url for navigation ('http://localhost:9876/test/qunit-test.js' or 'test/qunit-test.js') * @param lineNumber zero-based line number for navigation * @param columnNumber zero-based column number for navigation * @return computed HyperlinkInfo instance of null if {@code url} matches no files. */ @Nullable private HyperlinkInfo createHyperlinkInfoFromParts(@NotNull String url, final int lineNumber, @Nullable final Integer columnNumber) { final File file = findFileByPath(url); if (file == null) { return null; } return new HyperlinkInfo() { @Override public void navigate(final Project project) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByIoFile(file); if (virtualFile != null) { Document document = FileDocumentManager.getInstance().getDocument(virtualFile); if (document != null) { int startLineNumber = document.getLineStartOffset(lineNumber); int resultOffset = startLineNumber + (columnNumber != null ? columnNumber : 0); OpenFileDescriptor openFileDescriptor = new OpenFileDescriptor(project, virtualFile, resultOffset); openFileDescriptor.navigate(true); } } } }); } }; } private File findFileByPath(String urlStr) { File file = myConfigStructure.findLoadFile(urlStr); if (file != null) { return file; } try { URL url = new URL(urlStr); String path = url.getPath(); if (path.startsWith(DEFAULT_PATH_PREFIX)) { path = path.substring(DEFAULT_PATH_PREFIX.length()); } return myConfigStructure.findLoadFile(path); } catch (MalformedURLException ignored) { } return null; } private static abstract class HyperlinkInfoHelper { abstract Pattern[] getAllPossibleUrlPatterns(); int zeroBaseLineNo(int lineNo) { return lineNo - 1; } int zeroBaseColumnNo(int columnNo) { return columnNo - 1; } } private static class ChromeHyperlinkInfoHelper extends HyperlinkInfoHelper { private static final ChromeHyperlinkInfoHelper INSTANCE = new ChromeHyperlinkInfoHelper(); private static final Pattern[] URL_PATTERNS = { Pattern.compile("^\\s*at\\s.*\\(([^\\(]*)\\)$"), Pattern.compile("^\\s*at\\s*(http://[^\\(]*)$") }; @Override Pattern[] getAllPossibleUrlPatterns() { return URL_PATTERNS; } } private static class FirefoxHyperlinkInfoHelper extends HyperlinkInfoHelper { private static final FirefoxHyperlinkInfoHelper INSTANCE = new FirefoxHyperlinkInfoHelper(); private static final Pattern[] FIREFOX_URL_WITH_LINE = { Pattern.compile("^\\s*\\w*\\(.*\\)@([^\\(]*)$") }; @Override Pattern[] getAllPossibleUrlPatterns() { return FIREFOX_URL_WITH_LINE; } } }