package org.jetbrains.plugins.ruby.motion.run;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.process.AnsiEscapeDecoder;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.execution.process.UnixProcessManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Processor;
import com.intellij.util.TimeoutUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.cidr.execution.ExecutionResult;
import com.jetbrains.cidr.execution.ProcessHandlerWithPID;
import com.jetbrains.cidr.execution.debugger.CidrDebuggerLog;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ruby.console.RubyConsoleProcessHandler;
import org.jetbrains.plugins.ruby.console.RubyLanguageConsole;
import org.jetbrains.plugins.ruby.motion.RubyMotionUtil;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Dennis.Ushakov
*/
public class SimulatorConsoleProcessHandler extends RubyConsoleProcessHandler implements ProcessHandlerWithPID,
ProcessHandlerWithDetachSemaphore,
AnsiEscapeDecoder.ColoredChunksAcceptor {
private static final Pattern PROMPT = Pattern.compile("(\\(\\S*\\)[>?]).*");
private final ExecutionResult<Integer> myPIDResult = new ExecutionResult<>();
private final MotionSimulatorRunExtension.MotionProcessOutputReaders myReaders;
private String myLastMatched;
private boolean mySimulateStarted = false;
private final boolean isOSX;
private Semaphore myDetachSemaphore;
public SimulatorConsoleProcessHandler(@NotNull final Module module,
Process process,
String commandLine,
MotionSimulatorRunExtension.MotionProcessOutputReaders readers) {
super(process, commandLine);
myReaders = readers;
myReaders.setHandler(this);
isOSX = RubyMotionUtil.getInstance().isOSX(module);
}
@Override
public void setConsole(@NotNull RubyLanguageConsole console) {
super.setConsole(console);
console.setPrompt("(main)>");
}
@Override
public void coloredChunksAvailable(@NotNull List<Pair<String, Key>> textChunks) {
super.coloredChunksAvailable(preprocessText(textChunks));
}
@NotNull
private List<Pair<String, Key>> preprocessText(@NotNull List<Pair<String, Key>> textChunks) {
final List<Pair<String, Key>> textToProcess = ContainerUtil.newArrayList();
for (final Pair<String, Key> chunk : textChunks) {
final String text = chunk.getFirst();
final Key attributes = chunk.getSecond();
mySimulateStarted |= text.contains(isOSX ? "Run" : "Simulate");
final String[] lines = text.split("\r");
findPid(lines, mySimulateStarted, myPIDResult);
if (attributes != ProcessOutputTypes.STDOUT) {
textToProcess.add(Pair.create(text, attributes));
continue;
}
for (final String line : lines) {
if (StringUtil.isEmpty(line.trim())) continue;
if (StringUtil.equals(myLastMatched, line)) continue;
final Matcher matcher = PROMPT.matcher(line);
if (matcher.matches()) {
final String prompt = matcher.group(1);
getConsole().setPrompt(prompt);
myLastMatched = line;
} else {
textToProcess.add(Pair.create(line, attributes));
myLastMatched = null;
}
}
}
return textToProcess;
}
static void findPid(String[] lines, final boolean simulateStarted, final ExecutionResult<Integer> pidResult) {
if (simulateStarted && !pidResult.isDone()) {
for (String line : lines) {
if (line.contains(".app")) {
findPid(line.trim(), pidResult);
}
}
}
}
@Override
protected void destroyProcessImpl() {
try {
if (myDetachSemaphore != null) myDetachSemaphore.acquire();
} catch (InterruptedException e) {
CidrDebuggerLog.LOG.info(e);
}
super.destroyProcessImpl();
closeReaders();
}
private void closeReaders() {
ApplicationManager.getApplication().executeOnPooledThread(() -> myReaders.close());
}
@Override
protected void detachProcessImpl() {
super.detachProcessImpl();
closeReaders();
}
private static void findPid(String line, final ExecutionResult<Integer> pidResult) {
final int i = line.lastIndexOf('/');
final String appName = i > 0 ? line.substring(i) : null;
if (appName == null) return;
ApplicationManager.getApplication().executeOnPooledThread(() -> {
// let's wait for an app for 3 minutes with 100ms interval
for (int i1 = 0; i1 < 3 * 60 * 10; i1++) {
UnixProcessManager.processPSOutput(UnixProcessManager.getPSCmd(false, true), s -> {
final Scanner scanner = new Scanner(s);
final int ppid = scanner.nextInt();
final int pid = scanner.nextInt();
final String command = scanner.nextLine();
if (command.contains(appName) && !pidResult.isDone()) {
pidResult.set(pid);
return true;
}
return false;
});
if (pidResult.isDone()) {
return;
}
TimeoutUtil.sleep(100);
}
CidrDebuggerLog.LOG.error("Failed to find pid for: " + appName);
});
}
@Override
public int getPID() throws ExecutionException {
return myPIDResult.get();
}
@Override
public int getPID(long timeout) throws ExecutionException, TimeoutException {
return myPIDResult.get(timeout, TimeUnit.MILLISECONDS);
}
@Override
public void setDetachSemaphore(Semaphore detachSemaphore) {
myDetachSemaphore = detachSemaphore;
}
}