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;