diff --git a/dlangide-monod-linux.dproj b/dlangide-monod-linux.dproj
index bfb21df..1b6ebdf 100644
--- a/dlangide-monod-linux.dproj
+++ b/dlangide-monod-linux.dproj
@@ -204,6 +204,7 @@
+
diff --git a/dlangide-monod-osx.dproj b/dlangide-monod-osx.dproj
index e25370b..89f0ac5 100644
--- a/dlangide-monod-osx.dproj
+++ b/dlangide-monod-osx.dproj
@@ -113,6 +113,7 @@
+
diff --git a/dlangide_msvc.visualdproj b/dlangide_msvc.visualdproj
index 2103635..089eb3a 100644
--- a/dlangide_msvc.visualdproj
+++ b/dlangide_msvc.visualdproj
@@ -425,7 +425,7 @@
-
+
@@ -452,6 +452,7 @@
+
diff --git a/src/ddebug/common/debugger.d b/src/ddebug/common/debugger.d
index 3176917..dfdf08b 100644
--- a/src/ddebug/common/debugger.d
+++ b/src/ddebug/common/debugger.d
@@ -3,6 +3,37 @@ module ddebug.common.debugger;
import core.thread;
import dlangui.core.logger;
import ddebug.common.queue;
+import ddebug.common.execution;
+
+enum DebuggingState {
+ loaded,
+ running,
+ paused,
+ stopped
+}
+
+interface DebuggerCallback : ProgramExecutionStatusListener {
+ /// debugger message line
+ void onDebuggerMessage(string msg);
+
+ /// debugger is started and loaded program, you can set breakpoints at this time
+ void onProgramLoaded(bool successful, bool debugInfoLoaded);
+
+ /// state changed: running / paused / stopped
+ void onDebugState(DebuggingState state, string msg, int param);
+
+ void onResponse(ResponseCode code, string msg);
+}
+
+interface Debugger : ProgramExecution {
+ void setDebuggerCallback(DebuggerCallback callback);
+ void setDebuggerExecutable(string debuggerExecutable);
+
+ /// can be called after program is loaded
+ void execStart();
+ /// continue execution
+ void execContinue();
+}
enum ResponseCode : int {
/// Operation finished successfully
@@ -21,49 +52,135 @@ enum ResponseCode : int {
}
alias Runnable = void delegate();
-alias DebuggerResponse = void delegate(ResponseCode code, string msg);
-interface Debugger {
- /// start debugging
- void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response);
-}
+//interface Debugger {
+// /// start debugging
+// void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response);
+//}
/// proxy for debugger interface implementing async calls
-class DebuggerProxy : Debugger {
+class DebuggerProxy : Debugger, DebuggerCallback {
private DebuggerBase _debugger;
- private void delegate(Runnable runnable) _callbackDelegate;
+ private void delegate(void delegate() runnable) _callbackDelegate;
- this(DebuggerBase debugger, void delegate(Runnable runnable) callbackDelegate) {
+ this(DebuggerBase debugger, void delegate(void delegate() runnable) callbackDelegate) {
_debugger = debugger;
_callbackDelegate = callbackDelegate;
}
- void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response) {
- _debugger.postRequest(delegate() {
- _debugger.startDebugging(debuggerExecutable, executable, args, workingDir,
- delegate(ResponseCode code, string msg) {
- _callbackDelegate( delegate() { response(code, msg); } );
- }
- );
- });
- }
+ /// returns true if it's debugger
+ @property bool isDebugger() { return true; }
+ /// executable file
+ @property string executableFile() { return _debugger.executableFile; }
+ /// returns execution status
+ //@property ExecutionStatus status();
+ void setExecutableParams(string executableFile, string[] args, string workingDir, string[string] envVars) {
+ _debugger.setExecutableParams(executableFile, args, workingDir, envVars);
+ }
+
+ /// set external terminal parameters before execution
+ void setTerminalExecutable(string terminalExecutable) {
+ _debugger.setTerminalExecutable(terminalExecutable);
+ }
+
+ /// set debugger executable
+ void setDebuggerExecutable(string debuggerExecutable) {
+ _debugger.setDebuggerExecutable(debuggerExecutable);
+ }
+
+ protected DebuggerCallback _callback;
+ /// set debugger callback
+ void setDebuggerCallback(DebuggerCallback callback) {
+ _callback = callback;
+ _debugger.setDebuggerCallback(this);
+ }
+
+ /// called when program execution is stopped
+ void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) {
+ DebuggerProxy proxy = this;
+ _callbackDelegate( delegate() { _callback.onProgramExecutionStatus(proxy, status, exitCode); } );
+ }
+
+ /// debugger is started and loaded program, you can set breakpoints at this time
+ void onProgramLoaded(bool successful, bool debugInfoLoaded) {
+ _callbackDelegate( delegate() { _callback.onProgramLoaded(successful, debugInfoLoaded); } );
+ }
+
+ /// state changed: running / paused / stopped
+ void onDebugState(DebuggingState state, string msg, int param) {
+ _callbackDelegate( delegate() { _callback.onDebugState(state, msg, param); } );
+ }
+
+ void onResponse(ResponseCode code, string msg) {
+ _callbackDelegate( delegate() { _callback.onResponse(code, msg); } );
+ }
+
+ void onDebuggerMessage(string msg) {
+ _callbackDelegate( delegate() { _callback.onDebuggerMessage(msg); } );
+ }
+
+ /// start execution
+ void run() {
+ Log.d("DebuggerProxy.run()");
+ _debugger.run();
+ //_debugger.postRequest(delegate() { _debugger.run(); });
+ }
+ /// stop execution
+ void stop() {
+ Log.d("DebuggerProxy.stop()");
+ _debugger.stop();
+ //_debugger.postRequest(delegate() { _debugger.stop(); });
+ }
+
+ /// start execution, can be called after program is loaded
+ void execStart() {
+ _debugger.postRequest(delegate() { _debugger.execStart(); });
+ }
+ /// continue program
+ void execContinue() {
+ _debugger.postRequest(delegate() { _debugger.execContinue(); });
+ }
}
-class DebuggerBase : Thread, Debugger {
+abstract class DebuggerBase : Thread, Debugger {
+ private bool _runRequested;
private bool _stopRequested;
private bool _finished;
- protected string _debuggerExecutable;
protected BlockingQueue!Runnable _queue;
+ protected ExecutionStatus _status = ExecutionStatus.NotStarted;
+ protected int _exitCode = 0;
+
+ /// provides _executableFile, _executableArgs, _executableWorkingDir, _executableEnvVars parameters and setter function setExecutableParams
+ mixin ExecutableParams;
+ /// provides _terminalExecutable and setTerminalExecutable setter
+ mixin TerminalParams;
+
+ protected DebuggerCallback _callback;
+ void setDebuggerCallback(DebuggerCallback callback) {
+ _callback = callback;
+ }
+
+ protected string _debuggerExecutable;
+ void setDebuggerExecutable(string debuggerExecutable) {
+ _debuggerExecutable = debuggerExecutable;
+ }
+
+ @property bool isDebugger() { return true; }
+
+ @property string executableFile() {
+ return _executableFile;
+ }
+
void postRequest(Runnable request) {
_queue.put(request);
}
this() {
- super(&run);
+ super(&threadFunc);
_queue = new BlockingQueue!Runnable();
}
@@ -73,8 +190,23 @@ class DebuggerBase : Thread, Debugger {
_queue = null;
}
+ // call from GUI thread
+ void run() {
+ Log.d("DebuggerBase.run()");
+ assert(!_runRequested);
+ _runRequested = true;
+ postRequest(&startDebugging);
+ start();
+ }
+
+ void startDebugging() {
+ // override to implement
+ }
+
void stop() {
Log.i("Debugger.stop()");
+ if (_stopRequested)
+ return;
_stopRequested = true;
_queue.close();
}
@@ -83,25 +215,26 @@ class DebuggerBase : Thread, Debugger {
}
protected void onDebuggerThreadFinished() {
+ _callback.onProgramExecutionStatus(this, _status, _exitCode);
}
/// thread func: execute all tasks from queue
- private void run() {
+ private void threadFunc() {
onDebuggerThreadStarted();
Log.i("Debugger thread started");
- while (!_stopRequested) {
- Runnable task;
- if (_queue.get(task, 0)) {
- task();
- }
- }
+ try {
+ while (!_stopRequested) {
+ Runnable task;
+ if (_queue.get(task, 0)) {
+ task();
+ }
+ }
+ } catch (Exception e) {
+ Log.e("Exception in debugger thread");
+ }
Log.i("Debugger thread finished");
_finished = true;
onDebuggerThreadFinished();
}
- void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response) {
- response(ResponseCode.NotImplemented, "Not Implemented");
- }
-
}
diff --git a/src/ddebug/common/execution.d b/src/ddebug/common/execution.d
index 6dc35d5..d7330de 100644
--- a/src/ddebug/common/execution.d
+++ b/src/ddebug/common/execution.d
@@ -17,15 +17,51 @@ interface ProgramExecutionStatusListener {
void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode);
}
+// Interface to run program and control program execution
interface ProgramExecution {
+ /// set executable parameters before execution
+ void setExecutableParams(string executableFile, string[] args, string workingDir, string[string] envVars);
+ /// set external terminal parameters before execution
+ void setTerminalExecutable(string terminalExecutable);
+
/// returns true if it's debugger
@property bool isDebugger();
/// executable file
@property string executableFile();
/// returns execution status
- @property ExecutionStatus status();
+ //@property ExecutionStatus status();
/// start execution
- bool run();
+ void run();
/// stop execution
- bool stop();
+ void stop();
}
+
+/// provides _executableFile, _executableArgs, _executableWorkingDir, _executableEnvVars parameters and setter function setExecutableParams
+mixin template ExecutableParams() {
+ protected string _executableFile;
+ protected string[] _executableArgs;
+ protected string _executableWorkingDir;
+ protected string[string] _executableEnvVars;
+
+ /// set executable parameters before execution
+ void setExecutableParams(string executableFile, string[] args, string workingDir, string[string] envVars) {
+ _executableFile = executableFile;
+ _executableArgs = args;
+ _executableWorkingDir = workingDir;
+ _executableEnvVars = envVars;
+ }
+}
+
+
+/// provides _terminalExecutable and setTerminalExecutable setter
+mixin template TerminalParams() {
+
+ /// executable file name for external console/terminal
+ protected string _terminalExecutable;
+
+ /// set external terminal parameters before execution
+ void setTerminalExecutable(string terminalExecutable) {
+ _terminalExecutable = terminalExecutable;
+ }
+}
+
diff --git a/src/ddebug/common/nodebug.d b/src/ddebug/common/nodebug.d
index 88b8e40..b8f86ae 100644
--- a/src/ddebug/common/nodebug.d
+++ b/src/ddebug/common/nodebug.d
@@ -9,29 +9,24 @@ import dlangui.core.logger;
class ProgramExecutionNoDebug : Thread, ProgramExecution {
// parameters
- protected string _executableFile;
- protected string[] _args;
- protected string _workDir;
- protected string _externalConsole;
- protected ProgramExecutionStatusListener _listener;
+ /// provides _executableFile, _executableArgs, _executableWorkingDir, _executableEnvVars parameters and setter function setExecutableParams
+ mixin ExecutableParams;
+ /// provides _terminalExecutable and setTerminalExecutable setter
+ mixin TerminalParams;
+ protected ProgramExecutionStatusListener _listener;
+ void setProgramExecutionStatusListener(ProgramExecutionStatusListener listener) {
+ _listener = listener;
+ }
// status
protected Pid _pid;
protected ExecutionStatus _status = ExecutionStatus.NotStarted;
protected int _exitCode = 0;
-
-
/// initialize but do not run
- this(string executable, string[] args, string workDir, string externalConsole, ProgramExecutionStatusListener listener) {
+ this() {
super(&threadFunc);
- _executableFile = executable;
- _args = args;
- _workDir = workDir;
- _externalConsole = externalConsole;
- _listener = listener;
- assert(_listener !is null);
}
~this() {
@@ -75,13 +70,13 @@ class ProgramExecutionNoDebug : Thread, ProgramExecution {
// prepare parameter list
string[] params;
params ~= _executableFile;
- params ~= _args;
+ params ~= _executableArgs;
// external console support
- if (!_externalConsole.empty) {
+ if (!_terminalExecutable.empty) {
string cmdline = escapeShellCommand(params);
params.length = 0;
- params ~= _externalConsole;
+ params ~= _terminalExecutable;
params ~= "-e";
params ~= cmdline;
}
@@ -96,7 +91,7 @@ class ProgramExecutionNoDebug : Thread, ProgramExecution {
newstderr = stderr;
}
try {
- _pid = spawnProcess(params, newstdin, newstdout, newstderr, null, Config.none, _workDir);
+ _pid = spawnProcess(params, newstdin, newstdout, newstderr, null, Config.none, _executableWorkingDir);
} catch (Exception e) {
Log.e("ProgramExecutionNoDebug: Failed to spawn process: ", e);
killProcess();
@@ -135,28 +130,27 @@ class ProgramExecutionNoDebug : Thread, ProgramExecution {
@property ExecutionStatus status() { return _status; }
/// start execution
- bool run() {
+ void run() {
if (_runRequested)
- return false; // already running
+ return; // already running
+ assert(_listener !is null);
_runRequested = true;
_threadStarted = true;
_status = ExecutionStatus.Running;
start();
- return true;
}
/// stop execution (call from GUI thread)
- bool stop() {
+ void stop() {
if (!_runRequested)
- return false;
+ return;
if (_stopRequested)
- return true;
+ return;
_stopRequested = true;
if (_threadStarted && !_threadJoined) {
_threadJoined = true;
join();
}
- return true;
}
protected bool _threadStarted;
diff --git a/src/ddebug/gdb/gdbinterface.d b/src/ddebug/gdb/gdbinterface.d
index b307861..9cd66e2 100644
--- a/src/ddebug/gdb/gdbinterface.d
+++ b/src/ddebug/gdb/gdbinterface.d
@@ -1,13 +1,16 @@
module ddebug.gdb.gdbinterface;
public import ddebug.common.debugger;
+import ddebug.common.execution;
import dlangui.core.logger;
import ddebug.common.queue;
import dlangide.builders.extprocess;
import std.utf;
import std.conv : to;
+import std.array : empty;
+import std.algorithm : startsWith, equal;
-class ConsoleDebuggerInterface : DebuggerBase, TextWriter {
+abstract class ConsoleDebuggerInterface : DebuggerBase, TextWriter {
protected ExternalProcess _debuggerProcess;
protected ExternalProcessState runDebuggerProcess(string executable, string[]args, string dir) {
@@ -20,8 +23,13 @@ class ConsoleDebuggerInterface : DebuggerBase, TextWriter {
private char[] _stdoutBuf;
/// return true to clear lines list
protected bool onDebuggerStdoutLines(string[] lines) {
+ foreach(line; lines) {
+ onDebuggerStdoutLine(line);
+ }
return true;
}
+ protected void onDebuggerStdoutLine(string line) {
+ }
private void onStdoutText(string text) {
_stdoutBuf ~= text;
// pass full lines
@@ -74,14 +82,16 @@ class GDBInterface : ConsoleDebuggerInterface {
int sendCommand(string text) {
commandId++;
- sendLine(to!string(commandId) ~ text);
+ string cmd = to!string(commandId) ~ text;
+ Log.d("GDB command[", commandId, "]> ", text);
+ sendLine(cmd);
return commandId;
}
Pid terminalPid;
string terminalTty;
- string startTerminal(string termExecutable) {
+ string startTerminal() {
Log.d("Starting terminal");
import std.random;
import std.file;
@@ -94,7 +104,7 @@ class GDBInterface : ConsoleDebuggerInterface {
Log.d("temp file for tty name: ", termfile);
try {
terminalPid = spawnProcess([
- termExecutable,
+ _terminalExecutable,
"-title",
"DLangIDE External Console",
"-e",
@@ -133,6 +143,8 @@ class GDBInterface : ConsoleDebuggerInterface {
}
bool isTerminalActive() {
+ if (_terminalExecutable.empty)
+ return true;
if (terminalPid is null)
return false;
auto res = tryWait(terminalPid);
@@ -147,6 +159,8 @@ class GDBInterface : ConsoleDebuggerInterface {
}
void killTerminal() {
+ if (_terminalExecutable.empty)
+ return;
if (terminalPid is null)
return;
try {
@@ -162,44 +176,253 @@ class GDBInterface : ConsoleDebuggerInterface {
}
}
- string terminalExecutableFileName = "xterm";
- override void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response) {
+ override void startDebugging() {
+ Log.d("GDBInterface.startDebugging()");
string[] debuggerArgs;
- terminalTty = startTerminal(terminalExecutableFileName);
- if (terminalTty.length == 0) {
- response(ResponseCode.CannotRunDebugger, "Cannot start terminal");
- return;
- }
- debuggerArgs ~= "-tty";
- debuggerArgs ~= terminalTty;
+ if (!_terminalExecutable.empty) {
+ terminalTty = startTerminal();
+ if (terminalTty.length == 0) {
+ _callback.onResponse(ResponseCode.CannotRunDebugger, "Cannot start terminal");
+ _status = ExecutionStatus.Error;
+ _callback.onProgramExecutionStatus(this, _status, _exitCode);
+ return;
+ }
+ debuggerArgs ~= "-tty";
+ debuggerArgs ~= terminalTty;
+ }
debuggerArgs ~= "--interpreter=mi";
debuggerArgs ~= "--silent";
debuggerArgs ~= "--args";
- debuggerArgs ~= executable;
- foreach(arg; args)
+ debuggerArgs ~= _executableFile;
+ foreach(arg; _executableArgs)
debuggerArgs ~= arg;
- ExternalProcessState state = runDebuggerProcess(debuggerExecutable, debuggerArgs, workingDir);
+ ExternalProcessState state = runDebuggerProcess(_debuggerExecutable, debuggerArgs, _executableWorkingDir);
Log.i("Debugger process state:");
if (state == ExternalProcessState.Running) {
- response(ResponseCode.Ok, "Started");
+ _callback.onProgramLoaded(true, true);
//sendCommand("-break-insert main");
- sendCommand("-exec-run");
} else {
- response(ResponseCode.CannotRunDebugger, "Error while trying to run debugger process");
+ _status = ExecutionStatus.Error;
+ _callback.onProgramExecutionStatus(this, _status, _exitCode);
return;
}
- }
+ }
override void stop() {
+ Log.d("GDBInterface.run()");
if (_debuggerProcess !is null)
_debuggerProcess.kill();
killTerminal();
super.stop();
}
- /// return true to clear lines list
- override protected bool onDebuggerStdoutLines(string[] lines) {
- Log.d("onDebuggerStdout ", lines);
- return true;
- }
+ /// start program execution, can be called after program is loaded
+ int _startRequestId;
+ void execStart() {
+ _startRequestId = sendCommand("-exec-run");
+ }
+
+ /// start program execution, can be called after program is loaded
+ int _continueRequestId;
+ void execContinue() {
+ _continueRequestId = sendCommand("-exec-continue");
+ }
+
+ // ~message
+ void handleStreamLineCLI(string s) {
+ Log.d("GDB CLI: ", s);
+ _callback.onDebuggerMessage(s);
+ }
+
+ // @message
+ void handleStreamLineProgram(string s) {
+ Log.d("GDB program stream: ", s);
+ //_callback.onDebuggerMessage(s);
+ }
+
+ // &message
+ void handleStreamLineGDBDebug(string s) {
+ Log.d("GDB internal debug message: ", s);
+ }
+
+ // *stopped,reason="exited-normally"
+ // *running,thread-id="all"
+ // *asyncclass,result
+ void handleExecAsyncMessage(uint token, string s) {
+ string msgType = parseIdentAndSkipComma(s);
+ AsyncClass msgId = asyncByName(msgType);
+ if (msgId == AsyncClass.other)
+ Log.d("GDB WARN unknown async class type: ", msgType);
+ Log.v("GDB async *[", token, "] ", msgType, " params: ", s);
+ if (msgId == AsyncClass.running) {
+ _callback.onDebugState(DebuggingState.running, s, 0);
+ } else if (msgId == AsyncClass.stopped) {
+ _callback.onDebugState(DebuggingState.stopped, s, 0);
+ }
+ }
+
+ // +asyncclass,result
+ void handleStatusAsyncMessage(uint token, string s) {
+ string msgType = parseIdentAndSkipComma(s);
+ AsyncClass msgId = asyncByName(msgType);
+ if (msgId == AsyncClass.other)
+ Log.d("GDB WARN unknown async class type: ", msgType);
+ Log.v("GDB async +[", token, "] ", msgType, " params: ", s);
+ }
+
+ // =asyncclass,result
+ void handleNotifyAsyncMessage(uint token, string s) {
+ string msgType = parseIdentAndSkipComma(s);
+ AsyncClass msgId = asyncByName(msgType);
+ if (msgId == AsyncClass.other)
+ Log.d("GDB WARN unknown async class type: ", msgType);
+ Log.v("GDB async =[", token, "] ", msgType, " params: ", s);
+ }
+
+ // ^resultClass,result
+ void handleResultMessage(uint token, string s) {
+ string msgType = parseIdentAndSkipComma(s);
+ ResultClass msgId = resultByName(msgType);
+ if (msgId == ResultClass.other)
+ Log.d("GDB WARN unknown result class type: ", msgType);
+ Log.v("GDB result ^[", token, "] ", msgType, " params: ", s);
+ }
+
+ bool _firstIdle = true;
+ // (gdb)
+ void onDebuggerIdle() {
+ Log.d("GDB idle");
+ if (_firstIdle) {
+ _firstIdle = false;
+ return;
+ }
+ }
+
+ override protected void onDebuggerStdoutLine(string gdbLine) {
+ //Log.d("GDB stdout: '", line, "'");
+ string line = gdbLine;
+ if (line.empty)
+ return;
+ // parse token (sequence of digits at the beginning of message)
+ uint tokenId = 0;
+ int tokenLen = 0;
+ while (tokenLen < line.length && line[tokenLen] >= '0' && line[tokenLen] <= '9')
+ tokenLen++;
+ if (tokenLen > 0) {
+ tokenId = to!uint(line[0..tokenLen]);
+ line = line[tokenLen .. $];
+ }
+ if (line.length == 0)
+ return; // token only, no message!
+ char firstChar = line[0];
+ string restLine = line.length > 1 ? line[1..$] : "";
+ if (firstChar == '~') {
+ handleStreamLineCLI(restLine);
+ return;
+ } else if (firstChar == '@') {
+ handleStreamLineProgram(restLine);
+ return;
+ } else if (firstChar == '&') {
+ handleStreamLineGDBDebug(restLine);
+ return;
+ } else if (firstChar == '*') {
+ handleExecAsyncMessage(tokenId, restLine);
+ return;
+ } else if (firstChar == '+') {
+ handleStatusAsyncMessage(tokenId, restLine);
+ return;
+ } else if (firstChar == '=') {
+ handleNotifyAsyncMessage(tokenId, restLine);
+ return;
+ } else if (firstChar == '^') {
+ handleResultMessage(tokenId, restLine);
+ return;
+ } else if (line.startsWith("(gdb)")) {
+ onDebuggerIdle();
+ return;
+ } else {
+ Log.d("GDB unprocessed: ", gdbLine);
+ }
+ }
+
+}
+
+string parseIdent(ref string s) {
+ string res = null;
+ int len = 0;
+ for(; len < s.length; len++) {
+ char ch = s[len];
+ if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '-'))
+ break;
+ }
+ if (len > 0) {
+ res = s[0..len];
+ s = s[len .. $];
+ }
+ return res;
+}
+
+bool skipComma(ref string s) {
+ if (s.length > 0 && s[0] == ',') {
+ s = s[1 .. $];
+ return true;
+ }
+ return false;
+}
+
+string parseIdentAndSkipComma(ref string s) {
+ string res = parseIdent(s);
+ skipComma(s);
+ return res;
+}
+
+ResultClass resultByName(string s) {
+ if (s.equal("done")) return ResultClass.done;
+ if (s.equal("running")) return ResultClass.running;
+ if (s.equal("connected")) return ResultClass.connected;
+ if (s.equal("error")) return ResultClass.error;
+ if (s.equal("exit")) return ResultClass.exit;
+ return ResultClass.other;
+}
+
+enum ResultClass {
+ done,
+ running,
+ connected,
+ error,
+ exit,
+ other
+}
+
+AsyncClass asyncByName(string s) {
+ if (s.equal("stopped")) return AsyncClass.stopped;
+ if (s.equal("running")) return AsyncClass.running;
+ if (s.equal("library-loaded")) return AsyncClass.library_loaded;
+ if (s.equal("library-unloaded")) return AsyncClass.library_unloaded;
+ if (s.equal("thread-group-added")) return AsyncClass.thread_group_added;
+ if (s.equal("thread-group-started")) return AsyncClass.thread_group_started;
+ if (s.equal("thread-group-exited")) return AsyncClass.thread_group_exited;
+ if (s.equal("thread-created")) return AsyncClass.thread_created;
+ if (s.equal("thread-exited")) return AsyncClass.thread_exited;
+ return AsyncClass.other;
+}
+
+enum AsyncClass {
+ running,
+ stopped,
+ library_loaded,
+ library_unloaded,
+ thread_group_added,
+ thread_group_started,
+ thread_group_exited,
+ thread_created,
+ thread_exited,
+ other
+}
+
+enum MITokenType {
+ str,
+}
+
+struct MIToken {
}
diff --git a/src/dlangide.d b/src/dlangide.d
index 6d8421b..cacf705 100644
--- a/src/dlangide.d
+++ b/src/dlangide.d
@@ -44,24 +44,24 @@ extern (C) int UIAppMain(string[] args) {
//import ddc.lexer.tokenizer;
//runTokenizerTest();
- debug(DebugInfo) {
- version(USE_MAGO) {
- import ddebug.windows.mago;
- testMago();
- }
- version(Windows) {
- import ddebug.windows.debuginfo;
- import std.file;
- //debugInfoTest(thisExePath);
- }
- }
+ //debug(DebugInfo) {
+ // version(USE_MAGO) {
+ // import ddebug.windows.mago;
+ // testMago();
+ // }
+ // version(Windows) {
+ // import ddebug.windows.debuginfo;
+ // import std.file;
+ // //debugInfoTest(thisExePath);
+ // }
+ //}
- version(USE_WIN_DEBUG) {
- debuggerTest();
- }
- version(USE_GDB_DEBUG) {
- debuggerTestGDB();
- }
+ //version(USE_WIN_DEBUG) {
+ // debuggerTest();
+ //}
+ //version(USE_GDB_DEBUG) {
+ // debuggerTestGDB();
+ //}
// create window
Window window = Platform.instance.createWindow("Dlang IDE", null, WindowFlag.Resizable, 800, 600);
@@ -88,6 +88,7 @@ extern (C) int UIAppMain(string[] args) {
return Platform.instance.enterMessageLoop();
}
+/*
version(USE_WIN_DEBUG) {
void debuggerTest() {
import ddebug.windows.windebug;
@@ -129,6 +130,7 @@ version(USE_GDB_DEBUG) {
Log.d("Testing of GDB debugger is finished");
}
}
+*/
unittest {
void jsonTest() {
diff --git a/src/dlangide/ui/debuggerui.d b/src/dlangide/ui/debuggerui.d
new file mode 100644
index 0000000..b563028
--- /dev/null
+++ b/src/dlangide/ui/debuggerui.d
@@ -0,0 +1,55 @@
+module dlangide.ui.debuggerui;
+
+import dlangui.core.logger;
+import dlangide.ui.frame;
+import ddebug.common.execution;
+import ddebug.common.debugger;
+
+class DebuggerUIHandler : DebuggerCallback {
+ IDEFrame _ide;
+ Debugger _debugger;
+ DebuggingState _state = DebuggingState.loaded;
+
+ this(IDEFrame ide, Debugger debugger) {
+ _ide = ide;
+ _debugger = debugger;
+ _debugger.setDebuggerCallback(this);
+ }
+
+ /// called when program execution is stopped
+ void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) {
+ Log.d("Debugger exit status: ", status, " ", exitCode);
+ _ide.debugFinished(process, status, exitCode);
+ //_callbackDelegate( delegate() { _callback.onProgramExecutionStatus(this, status, exitCode); } );
+ }
+
+ void onResponse(ResponseCode code, string msg) {
+ Log.d("Debugger response: ", code, " ", msg);
+ //_callbackDelegate( delegate() { _callback.onResponse(code, msg); } );
+ }
+
+ void onDebuggerMessage(string msg) {
+ _ide.logPanel.logLine("DBG: " ~ msg);
+ }
+
+ /// debugger is started and loaded program, you can set breakpoints at this time
+ void onProgramLoaded(bool successful, bool debugInfoLoaded) {
+ _ide.logPanel.logLine("Program is loaded");
+ // TODO: check succes status and debug info
+ _debugger.execStart();
+ }
+
+ /// state changed: running / paused / stopped
+ void onDebugState(DebuggingState state, string msg, int param) {
+ Log.d("onDebugState: ", state, " ", msg, " param=", param);
+ _state = state;
+ if (state == DebuggingState.stopped) {
+ _ide.logPanel.logLine("Program is stopped: " ~ msg);
+ _debugger.stop();
+ }
+ }
+
+ void run() {
+ _debugger.run();
+ }
+}
diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d
index c89729f..8e80e74 100644
--- a/src/dlangide/ui/frame.d
+++ b/src/dlangide/ui/frame.d
@@ -25,6 +25,7 @@ import dlangide.ui.newproject;
import dlangide.ui.dsourceedit;
import dlangide.ui.homescreen;
import dlangide.ui.settings;
+import dlangide.ui.debuggerui;
import dlangide.tools.d.dcdserver;
import dlangide.workspace.workspace;
import dlangide.workspace.project;
@@ -33,6 +34,8 @@ import dlangide.tools.editorTool;
import ddebug.common.execution;
import ddebug.common.nodebug;
+import ddebug.common.debugger;
+import ddebug.gdb.gdbinterface;
import std.conv;
import std.utf;
@@ -88,6 +91,8 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
applySettings(_settings);
}
+ @property OutputPanel logPanel() { return _logPanel; }
+
/// stop current program execution
void stopExecution() {
if (_execution) {
@@ -128,6 +133,70 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
});
}
+ protected void buildAndDebugProject(Project project) {
+ if (!currentWorkspace)
+ return;
+ if (!project)
+ project = currentWorkspace.startupProject;
+ if (!project) {
+ window.showMessageBox(UIString("Cannot debug project"d), UIString("Startup project is not specified"d));
+ return;
+ }
+ buildProject(BuildOperation.Build, project, delegate(int result) {
+ if (!result) {
+ Log.i("Build completed successfully. Starting debug for project.");
+ debugProject(project);
+ }
+ });
+ }
+
+ void debugFinished(ProgramExecution process, ExecutionStatus status, int exitCode) {
+ _execution = null;
+ switch(status) {
+ case ExecutionStatus.Error:
+ _logPanel.logLine("Cannot run program " ~ process.executableFile);
+ break;
+ case ExecutionStatus.Finished:
+ _logPanel.logLine("Program " ~ process.executableFile ~ " finished with exit code " ~ to!string(exitCode));
+ break;
+ case ExecutionStatus.Killed:
+ _logPanel.logLine("Program " ~ process.executableFile ~ " is killed");
+ break;
+ default:
+ _logPanel.logLine("Program " ~ process.executableFile ~ " is finished");
+ break;
+ }
+ _statusLine.setBackgroundOperationStatus(null, null);
+ }
+
+ DebuggerUIHandler _debugHandler;
+ protected void debugProject(Project project) {
+ import std.file;
+ stopExecution();
+ if (!project) {
+ window.showMessageBox(UIString("Cannot debug project"d), UIString("Startup project is not specified"d));
+ return;
+ }
+ string executableFileName = project.executableFileName;
+ if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) {
+ window.showMessageBox(UIString("Cannot debug project"d), UIString("Cannot find executable file"d));
+ return;
+ }
+ string debuggerExecutable = _settings.debuggerExecutable;
+ if (debuggerExecutable.empty) {
+ window.showMessageBox(UIString("Cannot debug project"d), UIString("No debugger executable specified in settings"d));
+ return;
+ }
+
+ GDBInterface program = new GDBInterface();
+ DebuggerProxy debuggerProxy = new DebuggerProxy(program, &executeInUiThread);
+ setExecutableParameters(debuggerProxy, project, executableFileName);
+ debuggerProxy.setDebuggerExecutable(debuggerExecutable);
+ _execution = debuggerProxy;
+ _debugHandler = new DebuggerUIHandler(this, debuggerProxy);
+ _debugHandler.run();
+ }
+
protected void buildAndRunProject(Project project) {
if (!currentWorkspace)
return;
@@ -139,28 +208,32 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
}
buildProject(BuildOperation.Build, project, delegate(int result) {
if (!result) {
- runProject();
+ Log.i("Build completed successfully. Running program...");
+ runProject(project);
}
});
}
- protected void runProject() {
+ protected void runProject(Project project) {
import std.file;
stopExecution();
- if (!currentWorkspace)
- return;
- Project project = currentWorkspace.startupProject;
if (!project) {
window.showMessageBox(UIString("Cannot run project"d), UIString("Startup project is not specified"d));
return;
}
- // build project
- // TODO
string executableFileName = project.executableFileName;
if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) {
- window.showMessageBox(UIString("Cannot run project"d), UIString("Cannot find executable"d));
+ window.showMessageBox(UIString("Cannot run project"d), UIString("Cannot find executable file"d));
return;
}
+ ProgramExecutionNoDebug program = new ProgramExecutionNoDebug();
+ setExecutableParameters(program, project, executableFileName);
+ program.setProgramExecutionStatusListener(this);
+ _execution = program;
+ program.run();
+ }
+
+ bool setExecutableParameters(ProgramExecution program, Project project, string executableFileName) {
string[] args;
string externalConsoleExecutable = null;
string workingDirectory = project.workingDirectory;
@@ -170,11 +243,15 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
externalConsoleExecutable = _settings.terminalExecutable;
}
}
- // TODO: provide thread safe listener
- _logPanel.logLine("Starting " ~ executableFileName);
- _statusLine.setBackgroundOperationStatus("debug-run", "running..."d);
- _execution = new ProgramExecutionNoDebug(executableFileName, args, workingDirectory, externalConsoleExecutable, this);
- _execution.run();
+ if (!program.isDebugger)
+ _logPanel.logLine("Starting " ~ executableFileName);
+ else
+ _logPanel.logLine("Starting debugger for " ~ executableFileName);
+ _statusLine.setBackgroundOperationStatus("debug-run", program.isDebugger ? "debugging..."d : "running..."d);
+ string[string] env;
+ program.setExecutableParams(executableFileName, args, workingDirectory, env);
+ program.setTerminalExecutable(externalConsoleExecutable);
+ return true;
}
override protected void init() {
@@ -689,8 +766,10 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
case IDEActions.CleanWorkspace:
buildProject(BuildOperation.Clean, cast(Project)a.objectParam);
return true;
- case IDEActions.DebugStart:
case IDEActions.DebugStartNoDebug:
+ case IDEActions.DebugStart:
+ buildAndDebugProject(cast(Project)a.objectParam);
+ return true;
case IDEActions.DebugContinue:
buildAndRunProject(cast(Project)a.objectParam);
return true;