package com.intellij.javascript.karma.execution;
import com.intellij.execution.ExecutionResult;
import com.intellij.execution.configurations.RunProfileState;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.testframework.PoolOfTestIcons;
import com.intellij.execution.testframework.TestConsoleProperties;
import com.intellij.execution.testframework.TestTreeView;
import com.intellij.execution.testframework.sm.runner.SMTestProxy;
import com.intellij.execution.testframework.sm.runner.ui.SMRootTestProxyFormatter;
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView;
import com.intellij.execution.testframework.sm.runner.ui.TestTreeRenderer;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.execution.ui.ExecutionConsole;
import com.intellij.execution.ui.ExecutionConsoleEx;
import com.intellij.execution.ui.RunnerLayoutUi;
import com.intellij.execution.ui.layout.PlaceInGrid;
import com.intellij.icons.AllIcons;
import com.intellij.ide.browsers.OpenUrlHyperlinkInfo;
import com.intellij.javascript.debugger.JSDebugTabLayouter;
import com.intellij.javascript.debugger.JavaScriptDebugProcess;
import com.intellij.javascript.karma.server.KarmaServer;
import com.intellij.javascript.karma.server.KarmaServerLogComponent;
import com.intellij.javascript.karma.util.KarmaUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.content.Content;
import com.intellij.util.Alarm;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.debugger.connection.VmConnection;
public class KarmaConsoleView extends SMTRunnerConsoleView implements ExecutionConsoleEx {
private static final Logger LOG = Logger.getInstance(KarmaConsoleView.class);
private final KarmaServer myServer;
private final KarmaExecutionSession myExecutionSession;
public KarmaConsoleView(@NotNull TestConsoleProperties consoleProperties,
@NotNull KarmaServer server,
@NotNull KarmaExecutionSession executionSession) {
super(consoleProperties);
myServer = server;
myExecutionSession = executionSession;
}
@Override
public void buildUi(final RunnerLayoutUi ui) {
registerConsoleContent(ui);
registerKarmaServerTab(ui);
}
@Nullable
@Override
public String getExecutionConsoleId() {
return null;
}
@NotNull
private Content registerConsoleContent(@NotNull final RunnerLayoutUi ui) {
ui.getOptions().setMinimizeActionEnabled(false);
final Content consoleContent = ui.createContent(ExecutionConsole.CONSOLE_CONTENT_ID,
getComponent(),
"Test Run",
AllIcons.Debugger.Console,
getPreferredFocusableComponent());
ui.addContent(consoleContent, 1, PlaceInGrid.bottom, false);
consoleContent.setCloseable(false);
final KarmaRootTestProxyFormatter rootFormatter = new KarmaRootTestProxyFormatter(this, myServer);
if (myServer.areBrowsersReady()) {
KarmaUtil.selectAndFocusIfNotDisposed(ui, consoleContent, false, false);
}
else {
myServer.onPortBound(() -> {
KarmaUtil.selectAndFocusIfNotDisposed(ui, consoleContent, false, false);
scheduleBrowserCapturingSuggestion();
});
}
final ProcessAdapter listener = new ProcessAdapter() {
@Override
public void processTerminated(ProcessEvent event) {
if (myServer.getProcessHandler().isProcessTerminated()) {
rootFormatter.onServerProcessTerminated();
printServerFinishedInfo();
}
rootFormatter.onTestRunProcessTerminated();
}
};
myExecutionSession.getProcessHandler().addProcessListener(listener);
Disposer.register(this, new Disposable() {
@Override
public void dispose() {
myExecutionSession.getProcessHandler().removeProcessListener(listener);
}
});
return consoleContent;
}
private void scheduleBrowserCapturingSuggestion() {
final Alarm alarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
alarm.addRequest(() -> {
if (!myServer.getProcessHandler().isProcessTerminated() &&
!myServer.areBrowsersReady() &&
!Disposer.isDisposed(this)) {
printBrowserCapturingSuggestion();
}
Disposer.dispose(alarm);
}, 1000, ModalityState.any());
}
private void printBrowserCapturingSuggestion() {
SMTestProxy.SMRootTestProxy rootNode = getResultsViewer().getTestsRootNode();
rootNode.addLast(printer -> {
printer.print("To capture a browser open ", ConsoleViewContentType.SYSTEM_OUTPUT);
String url = myServer.formatUrl("/");
printer.printHyperlink(url, new OpenUrlHyperlinkInfo(url));
printer.print("\n", ConsoleViewContentType.SYSTEM_OUTPUT);
});
}
private void printServerFinishedInfo() {
SMTestProxy.SMRootTestProxy rootNode = getResultsViewer().getTestsRootNode();
rootNode.addSystemOutput("Karma server process terminated");
}
private void registerKarmaServerTab(@NotNull RunnerLayoutUi ui) {
KarmaServerLogComponent.register(getProperties().getProject(), myServer, ui, true);
}
@NotNull
public KarmaExecutionSession getKarmaExecutionSession() {
return myExecutionSession;
}
public JSDebugTabLayouter createDebugLayouter(@NotNull JavaScriptDebugProcess<?> debugProcess) {
return new KarmaDebugTabLayouter(debugProcess);
}
/**
* @return null in case of "Import Test Result" action
*/
@Nullable
public static KarmaConsoleView get(@NotNull ExecutionResult result, @NotNull RunProfileState state) {
ExecutionConsole console = result.getExecutionConsole();
if (console instanceof KarmaConsoleView) {
return (KarmaConsoleView)console;
}
Class consoleClass = console != null ? console.getClass() : null;
LOG.info("Cannot cast " + consoleClass + " to " + KarmaConsoleView.class.getSimpleName() +
", RunProfileState: " + state.getClass().getName());
return null;
}
private static class KarmaRootTestProxyFormatter implements SMRootTestProxyFormatter {
private final KarmaServer myServer;
private final TestTreeView myTreeView;
private boolean myTestRunProcessTerminated = false;
private boolean myServerProcessTerminated = false;
private KarmaRootTestProxyFormatter(@NotNull SMTRunnerConsoleView consoleView,
@NotNull KarmaServer server) {
myTreeView = consoleView.getResultsViewer().getTreeView();
myServer = server;
if (myTreeView != null) {
TestTreeRenderer originalRenderer = ObjectUtils.tryCast(myTreeView.getCellRenderer(), TestTreeRenderer.class);
if (originalRenderer != null) {
originalRenderer.setAdditionalRootFormatter(this);
}
}
}
private static void render(@NotNull TestTreeRenderer renderer, @NotNull String msg, boolean error) {
renderer.clear();
if (error) {
renderer.setIcon(PoolOfTestIcons.TERMINATED_ICON);
}
renderer.append(msg);
}
@Override
public void format(@NotNull SMTestProxy.SMRootTestProxy testProxy, @NotNull TestTreeRenderer renderer) {
if (testProxy.isLeaf()) {
if (myServerProcessTerminated) {
render(renderer, "Server is dead", true);
}
else {
if (myTestRunProcessTerminated) {
render(renderer, "Aborted", true);
}
else if (myServer.isPortBound() && !myServer.areBrowsersReady()) {
render(renderer, "Waiting for browser capturing...", false);
}
}
}
}
private void onTestRunProcessTerminated() {
myTestRunProcessTerminated = true;
myTreeView.repaint();
}
private void onServerProcessTerminated() {
myServerProcessTerminated = true;
myTreeView.repaint();
}
}
private class KarmaDebugTabLayouter extends JSDebugTabLayouter {
public KarmaDebugTabLayouter(@NotNull JavaScriptDebugProcess<? extends VmConnection> debugProcess) {
super(debugProcess);
}
@NotNull
@Override
public Content registerConsoleContent(@NotNull RunnerLayoutUi ui, @NotNull ExecutionConsole console) {
return KarmaConsoleView.this.registerConsoleContent(ui);
}
@Override
public void registerAdditionalContent(@NotNull RunnerLayoutUi ui) {
super.registerAdditionalContent(ui);
registerKarmaServerTab(ui);
}
}
}