package com.intellij.javascript.karma.debug;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.ExecutionResult;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.configurations.RunProfileState;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.runners.AsyncProgramRunner;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.ExecutionUtil;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.ide.browsers.WebBrowser;
import com.intellij.javascript.debugger.DebuggableFileFinder;
import com.intellij.javascript.debugger.JavaScriptDebugEngine;
import com.intellij.javascript.debugger.JavaScriptDebugProcess;
import com.intellij.javascript.debugger.RemoteDebuggingFileFinder;
import com.intellij.javascript.karma.KarmaConfig;
import com.intellij.javascript.karma.execution.KarmaConsoleView;
import com.intellij.javascript.karma.execution.KarmaRunConfiguration;
import com.intellij.javascript.karma.server.KarmaServer;
import com.intellij.javascript.karma.util.KarmaUtil;
import com.intellij.lang.javascript.modules.NodeModuleUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.ManagingFS;
import com.intellij.util.Alarm;
import com.intellij.util.Url;
import com.intellij.util.Urls;
import com.intellij.xdebugger.XDebugProcess;
import com.intellij.xdebugger.XDebugProcessStarter;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.XDebuggerManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.concurrency.Promises;
import org.jetbrains.debugger.connection.VmConnection;
import java.io.PrintWriter;
public class KarmaDebugProgramRunner extends AsyncProgramRunner {
private static final Logger LOG = Logger.getInstance(KarmaDebugProgramRunner.class);
@NotNull
@Override
public String getRunnerId() {
return "KarmaJavaScriptTestRunnerDebug";
}
@Override
public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
return DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) && profile instanceof KarmaRunConfiguration;
}
@NotNull
@Override
protected Promise<RunContentDescriptor> execute(@NotNull ExecutionEnvironment environment, @NotNull RunProfileState state) throws ExecutionException {
FileDocumentManager.getInstance().saveAllDocuments();
ExecutionResult executionResult = state.execute(environment.getExecutor(), this);
if (executionResult == null) {
return Promise.resolve(null);
}
KarmaConsoleView consoleView = KarmaConsoleView.get(executionResult, state);
if (consoleView == null) {
return Promise.resolve(KarmaUtil.createDefaultDescriptor(executionResult, environment));
}
KarmaServer karmaServer = consoleView.getKarmaExecutionSession().getKarmaServer();
if (karmaServer.areBrowsersReady()) {
KarmaDebugBrowserSelector browserSelector = new KarmaDebugBrowserSelector(
karmaServer.getCapturedBrowsers(),
environment,
consoleView
);
DebuggableWebBrowser debuggableWebBrowser = browserSelector.selectDebugEngine();
if (debuggableWebBrowser == null) {
return Promises.resolvedPromise(KarmaUtil.createDefaultDescriptor(executionResult, environment));
}
return KarmaKt.prepareKarmaDebugger(environment.getProject(), debuggableWebBrowser,
() -> createDescriptor(environment, executionResult, consoleView, karmaServer,
debuggableWebBrowser));
}
else {
RunContentDescriptor descriptor = KarmaUtil.createDefaultDescriptor(executionResult, environment);
karmaServer.onBrowsersReady(() -> ExecutionUtil.restartIfActive(descriptor));
return Promises.resolvedPromise(descriptor);
}
}
@NotNull
private static RunContentDescriptor createDescriptor(@NotNull ExecutionEnvironment environment,
@NotNull ExecutionResult executionResult,
@NotNull KarmaConsoleView consoleView,
@NotNull KarmaServer karmaServer,
@NotNull DebuggableWebBrowser debuggableWebBrowser) throws ExecutionException {
Url url = Urls.newFromEncoded(karmaServer.formatUrl("/"));
DebuggableFileFinder fileFinder = getDebuggableFileFinder(karmaServer);
XDebugSession session = XDebuggerManager.getInstance(environment.getProject()).startSession(
environment,
new XDebugProcessStarter() {
@Override
@NotNull
public XDebugProcess start(@NotNull XDebugSession session) {
JavaScriptDebugEngine debugEngine = debuggableWebBrowser.getDebugEngine();
WebBrowser browser = debuggableWebBrowser.getWebBrowser();
JavaScriptDebugProcess<? extends VmConnection> debugProcess =
debugEngine.createDebugProcess(session, browser, fileFinder, url, executionResult, true);
debugProcess.addFirstLineBreakpointPattern("\\.browserify$");
debugProcess.setElementsInspectorEnabled(false);
debugProcess.setConsoleMessagesSupportEnabled(false);
debugProcess.setLayouter(consoleView.createDebugLayouter(debugProcess));
Alarm alarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, consoleView);
alarm.addRequest(() -> {
resumeTestRunning(executionResult.getProcessHandler());
Disposer.dispose(alarm);
}, 2000);
return debugProcess;
}
}
);
return session.getRunContentDescriptor();
}
private static DebuggableFileFinder getDebuggableFileFinder(@NotNull KarmaServer karmaServer) {
BiMap<String, VirtualFile> mappings = HashBiMap.create();
KarmaConfig karmaConfig = karmaServer.getKarmaConfig();
if (karmaConfig != null) {
VirtualFile basePath = LocalFileSystem.getInstance().findFileByPath(karmaConfig.getBasePath());
if (basePath != null && basePath.isValid()) {
if (karmaConfig.isWebpack()) {
mappings.put("webpack:///" + basePath.getPath(), basePath);
VirtualFile nodeModulesDir = basePath.findChild(NodeModuleUtil.NODE_MODULES);
if (nodeModulesDir != null && nodeModulesDir.isValid() && nodeModulesDir.isDirectory()) {
mappings.put(karmaServer.formatUrlWithoutUrlRoot("/base/" + NodeModuleUtil.NODE_MODULES), nodeModulesDir);
}
}
else {
mappings.put(karmaServer.formatUrlWithoutUrlRoot("/base"), basePath);
}
}
}
if (SystemInfo.isWindows) {
VirtualFile[] roots = ManagingFS.getInstance().getLocalRoots();
for (VirtualFile root : roots) {
String key = karmaServer.formatUrlWithoutUrlRoot("/absolute" + root.getName());
if (mappings.containsKey(key)) {
LOG.warn("Duplicate mapping for Karma debug: " + key);
}
else {
mappings.put(key, root);
}
}
}
else {
VirtualFile[] roots = ManagingFS.getInstance().getLocalRoots();
if (roots.length == 1) {
mappings.put(karmaServer.formatUrlWithoutUrlRoot("/absolute"), roots[0]);
}
}
return new RemoteDebuggingFileFinder(mappings, null);
}
private static void resumeTestRunning(@NotNull ProcessHandler processHandler) {
if (processHandler instanceof OSProcessHandler) {
// process's input stream will be closed on process termination
@SuppressWarnings({"IOResourceOpenedButNotSafelyClosed", "ConstantConditions"})
PrintWriter writer = new PrintWriter(processHandler.getProcessInput());
writer.print("resume-test-running\n");
writer.flush();
}
}
}