/*
* Copyright 2011 Jon S Akhtar (Sylvanaar)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sylvanaar.idea.Lua.debugger;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
import com.intellij.xdebugger.frame.XSuspendContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by IntelliJ IDEA.
* User: Jon S Akhtar
* Date: 3/22/11
* Time: 6:31 PM
*/
public class LuaDebuggerController {
private static final Logger log = Logger.getInstance("Lua.LuaDebuggerController");
ServerSocket serverSocket;
Socket clientSocket;
int serverPort = 8171;
SocketReader reader = null;
OutputStream outputStream = null;
static final String RUN = "RUN\n";
static final String STEP = "STEP\n";
static final String STEP_OVER = "OVER\n";
private boolean readerCanRun = true;
Pattern AT_BREAKPOINT;
private XDebugSession session;
private ConsoleView console;
private boolean ready;
public XDebugSession getSession() { return session; }
Map<XBreakpoint, LuaPosition> myBreakpoints2Pos = new HashMap<XBreakpoint, LuaPosition>();
Map<LuaPosition, XBreakpoint> myPos2Breakpoints = new HashMap<LuaPosition, XBreakpoint>();
Project myProject = null;
LuaDebuggerController(XDebugSession session) {
myProject = session.getProject();
this.session = session;
this.session.setPauseActionSupported(false);
AT_BREAKPOINT = Pattern.compile("^202 Paused\\s+(\\S+)\\s+(\\d+)", Pattern.MULTILINE);
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
log.debug("Starting Debug Controller");
try {
serverSocket = new ServerSocket(serverPort);
log.debug("Accepting Connections");
clientSocket = serverSocket.accept();
log.debug("Client Connected " + clientSocket.getInetAddress());
} catch (IOException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
});
}
public void printToConsole(String text, ConsoleViewContentType contentType)
{
assert console != null;
console.print(text + '\n', contentType);
}
public void waitForConnect() throws IOException {
int count = 0;
while (clientSocket == null) {
try {
Thread.sleep(200);
if (++count > 20)
throw new RuntimeException("timeout");
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
printToConsole("Debugger connected at " + clientSocket.getInetAddress(), ConsoleViewContentType.SYSTEM_OUTPUT);
reader = new SocketReader();
reader.start();
outputStream = clientSocket.getOutputStream();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
ready = true;
}
public void terminate() {
log.debug("terminate");
readerCanRun = false;
try {
serverSocket.close();
if (clientSocket != null)
clientSocket.close();
ready = false;
} catch (IOException e) {
e.printStackTrace();
}
}
public void stepInto() {
try {
log.debug("stepInto");
outputStream.write(STEP.getBytes("UTF8"));
ready = false;
} catch (IOException e) {
e.printStackTrace();
}
}
public void stepOver() {
try {
log.debug("stepOver");
outputStream.write(STEP_OVER.getBytes("UTF8"));
ready = false;
} catch (IOException e) {
e.printStackTrace();
}
}
public void resume() {
try {
log.debug("resume");
outputStream.write(RUN.getBytes("UTF8"));
ready = false;
} catch (IOException e) {
e.printStackTrace();
}
}
public void setConsole(ConsoleView console) {
this.console = console;
}
public void addBreakPoint(XBreakpoint breakpoint) {
try {
LuaPosition pos = LuaPositionConverter.createRemotePosition(breakpoint.getSourcePosition());
String msg = String.format("SETB %s %d\n", pos.getPath(), pos.getLine());
log.debug(msg);
outputStream.write(msg.getBytes("UTF8"));
myBreakpoints2Pos.put(breakpoint, pos);
myPos2Breakpoints.put(pos, breakpoint);
// ready = false;
} catch (IOException e) {
e.printStackTrace();
}
}
public void removeBreakPoint(XBreakpoint breakpoint) {
try {
LuaPosition pos = LuaPositionConverter.createRemotePosition(breakpoint.getSourcePosition());
String msg = String.format("DELB %s %d\n", pos.getPath(), pos.getLine());
log.debug(msg);
outputStream.write(msg.getBytes("UTF8"));
myBreakpoints2Pos.remove(breakpoint);
myPos2Breakpoints.remove(pos);
// ready = false;
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean isReady() {
return ready;
}
Queue<CodeExecutionRequest> myPendingCallbacks = new ArrayBlockingQueue<CodeExecutionRequest>(5);
public void execute(CodeExecutionRequest codeExecutionRequest) {
myPendingCallbacks.add(codeExecutionRequest);
executePendingRequest();
}
private void executePendingRequest() {
CodeExecutionRequest codeExecutionRequest = myPendingCallbacks.peek();
if (codeExecutionRequest == null || codeExecutionRequest.isInProgress())
return;
codeExecutionRequest.setInProgress(true);
try {
String msg = String.format("EXEC %s\n", codeExecutionRequest.getCode());
log.debug(msg);
outputStream.write(msg.getBytes("UTF8"));
ready = false;
} catch (IOException e) {
e.printStackTrace();
}
}
class SocketReader extends Thread {
public SocketReader() {
super("DebuggerSocketReader");
}
@Override
public void run() {
log.debug("Read thread started");
byte[] buffer = new byte[1000];
InputStream input = null;
try {
input = clientSocket.getInputStream();
} catch (IOException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
while (readerCanRun) {
try {
while (input.available() > 0) {
assert input != null;
int count = 0;
if ((count = input.read(buffer)) > 0)
processResponse(new String(buffer, 0, count, CharsetToolkit.UTF8));
else
log.debug("No data to read");
}
} catch (IOException e) {
e.printStackTrace();
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
}
}
String cleanupFileName(String name) {
if (name.indexOf(':') != name.lastIndexOf(':')) {
int last = name.lastIndexOf(':');
return name.substring(last-1);
}
return name;
}
XSuspendContext EMPTY_CTX = new XSuspendContext() {};
private void processResponse(String messages) {
log.debug("Response: <"+messages+">");
String[] lines = messages.split("\n");
for (int i = 0, linesLength = lines.length; i < linesLength; i++) {
String message = lines[i];
log.debug("Processing: " + message);
if (message.startsWith("200")) {
processOK(message.substring(Math.min(7, message.length())));
continue;
}
Matcher m = AT_BREAKPOINT.matcher(message);
if (m.matches()) {
String file = m.group(1);
String line = m.group(2);
String stack = lines[i+1];
log.debug(String.format("break at <%s> line <%s> stack <%s>", file, line, stack));
LuaPosition position = new LuaPosition(file, Integer.parseInt(line));
XBreakpoint bp = myPos2Breakpoints.get(position);
ready = true;
LuaSuspendContext ctx;
if (bp != null)
ctx = new LuaSuspendContext(myProject, this, bp, stack);
else
ctx = new LuaSuspendContext(myProject, this, LuaPositionConverter.createLocalPosition(position), stack);
if (bp != null) session.breakpointReached(bp, null, ctx);
else { session.positionReached(ctx); }
// This makes the watch expressions update correctly at the start of a suspend context
// This is a hack.
// DebuggerUIUtil.invokeLater(new Runnable() {
// @Override
// public void run() {
// try {
// Thread.sleep(1500);
// } catch (InterruptedException e) {
// e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
// }
// XDebugSessionImpl xDebugSession = (XDebugSessionImpl) session;
// xDebugSession.activateSession();
// }
// });
continue;
}
}
}
private void processOK(String message) {
ready = true;
CodeExecutionRequest executionRequest = myPendingCallbacks.poll();
if (executionRequest != null && message.length() > 0) {
log.debug(String.format("Processing OK Payload: <%s>", message));
final int typeEnd = message.indexOf(' ');
String type = message.substring(0, typeEnd);
message = message.substring(typeEnd+1);
LuaDebugValue value = new LuaDebugValue(type, message);
executionRequest.getCallback().evaluated(value);
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
executePendingRequest();
}
});
}
}
static class CodeExecutionRequest {
private final String code;
private final XDebuggerEvaluator.XEvaluationCallback callback;
private boolean inProgress = false;
CodeExecutionRequest(String code, XDebuggerEvaluator.XEvaluationCallback callback) {
this.code = code;
this.callback = callback;
}
public String getCode() {
return code;
}
public XDebuggerEvaluator.XEvaluationCallback getCallback() {
return callback;
}
public boolean isInProgress() {
return inProgress;
}
public void setInProgress(boolean inProgress) {
this.inProgress = inProgress;
}
}
}