diff --git a/dlangide-monod-linux.dproj b/dlangide-monod-linux.dproj
index 72f829f..70f2bc5 100644
--- a/dlangide-monod-linux.dproj
+++ b/dlangide-monod-linux.dproj
@@ -218,6 +218,8 @@
+
+
diff --git a/dlangide-monod-osx.dproj b/dlangide-monod-osx.dproj
index b5b35e0..9ea4c6d 100644
--- a/dlangide-monod-osx.dproj
+++ b/dlangide-monod-osx.dproj
@@ -128,6 +128,8 @@
+
+
diff --git a/dlangide_msvc.visualdproj b/dlangide_msvc.visualdproj
index aaa86a9..6dd4474 100644
--- a/dlangide_msvc.visualdproj
+++ b/dlangide_msvc.visualdproj
@@ -419,6 +419,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/ddebug/common/execution.d b/src/ddebug/common/execution.d
new file mode 100644
index 0000000..6dc35d5
--- /dev/null
+++ b/src/ddebug/common/execution.d
@@ -0,0 +1,31 @@
+/**
+ Support for running stopping of project executable.
+
+ */
+module ddebug.common.execution;
+
+enum ExecutionStatus {
+ NotStarted,
+ Running,
+ Finished, // finished normally
+ Killed, // killed
+ Error // error while trying to start program
+}
+
+interface ProgramExecutionStatusListener {
+ /// called when program execution is stopped
+ void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode);
+}
+
+interface ProgramExecution {
+ /// returns true if it's debugger
+ @property bool isDebugger();
+ /// executable file
+ @property string executableFile();
+ /// returns execution status
+ @property ExecutionStatus status();
+ /// start execution
+ bool run();
+ /// stop execution
+ bool stop();
+}
diff --git a/src/ddebug/common/nodebug.d b/src/ddebug/common/nodebug.d
new file mode 100644
index 0000000..5dd5dad
--- /dev/null
+++ b/src/ddebug/common/nodebug.d
@@ -0,0 +1,146 @@
+module ddebug.common.nodebug;
+
+import ddebug.common.execution;
+
+import core.thread;
+import std.process;
+import dlangui.core.logger;
+
+class ProgramExecutionNoDebug : Thread, ProgramExecution {
+
+ // parameters
+ protected string _executableFile;
+ protected string[] _args;
+ protected string _workDir;
+ protected string _externalConsole;
+ protected ProgramExecutionStatusListener _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) {
+ super(&threadFunc);
+ _executableFile = executable;
+ _args = args;
+ _workDir = workDir;
+ _externalConsole = externalConsole;
+ _listener = listener;
+ assert(_listener !is null);
+ }
+
+ ~this() {
+ stop();
+ }
+
+ private bool isProcessActive() {
+ if (_pid is null)
+ return false;
+ auto res = tryWait(_pid);
+ if (res.terminated) {
+ Log.d("Process ", _executableFile, " is stopped");
+ _exitCode = wait(_pid);
+ _pid = Pid.init;
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private void killProcess() {
+ if (_pid is null)
+ return;
+ try {
+ Log.d("Trying to kill process", _executableFile);
+ kill(_pid, 9);
+ Log.d("Waiting for process termination");
+ _exitCode = wait(_pid);
+ _pid = Pid.init;
+ Log.d("Killed");
+ } catch (Exception e) {
+ Log.d("Exception while killing process " ~ _executableFile, e);
+ _pid = Pid.init;
+ }
+ }
+
+ private void threadFunc() {
+ import std.stdio;
+ string[] params;
+ params ~= _executableFile;
+ params ~= _args;
+ File newstdin;
+ File newstdout;
+ File newstderr;
+ try {
+ _pid = spawnProcess(params, newstdin, newstdout, newstderr, null, Config.none, _workDir);
+ } catch (Exception e) {
+ Log.e("ProgramExecutionNoDebug: Failed to spawn process: ", e);
+ killProcess();
+ _status = ExecutionStatus.Error;
+ }
+
+ if (_status != ExecutionStatus.Error) {
+ // thread loop: poll process status
+ while (!_stopRequested) {
+ Thread.sleep(dur!"msecs"(50));
+ if (!isProcessActive()) {
+ _status = ExecutionStatus.Finished;
+ break;
+ }
+ }
+ if (_stopRequested) {
+ killProcess();
+ _status = ExecutionStatus.Killed;
+ }
+ }
+
+ // finished
+ _listener.onProgramExecutionStatus(this, _status, _exitCode);
+ }
+
+ // implement ProgramExecution interface
+
+ /// returns true if it's debugger
+ @property bool isDebugger() { return false; }
+
+ /// executable file
+ @property string executableFile() { return _executableFile; }
+
+ /// returns execution status
+ @property ExecutionStatus status() { return _status; }
+
+ /// start execution
+ bool run() {
+ if (_runRequested)
+ return false; // already running
+ _runRequested = true;
+ _threadStarted = true;
+ _status = ExecutionStatus.Running;
+ start();
+ return true;
+ }
+
+ /// stop execution (call from GUI thread)
+ bool stop() {
+ if (!_runRequested)
+ return false;
+ if (_stopRequested)
+ return true;
+ _stopRequested = true;
+ if (_threadStarted && !_threadJoined) {
+ _threadJoined = true;
+ join();
+ }
+ return true;
+ }
+
+ protected bool _threadStarted;
+ protected bool _threadJoined;
+ protected bool _stopRequested;
+ protected bool _runRequested;
+}
diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d
index d1070f9..bf6005a 100644
--- a/src/dlangide/ui/frame.d
+++ b/src/dlangide/ui/frame.d
@@ -31,6 +31,9 @@ import dlangide.workspace.project;
import dlangide.builders.builder;
import dlangide.tools.editorTool;
+import ddebug.common.execution;
+import ddebug.common.nodebug;
+
import std.conv;
import std.utf;
import std.algorithm;
@@ -61,7 +64,7 @@ class BackgroundOperationWatcherTest : BackgroundOperationWatcher {
}
/// DIDE app frame
-class IDEFrame : AppFrame {
+class IDEFrame : AppFrame, ProgramExecutionStatusListener {
private ToolBarComboBox projectConfigurationCombo;
@@ -72,6 +75,7 @@ class IDEFrame : AppFrame {
TabWidget _tabs;
DCDServer _dcdServer;
IDESettings _settings;
+ ProgramExecution _execution;
dstring frameWindowCaptionSuffix = "DLangIDE"d;
@@ -84,6 +88,65 @@ class IDEFrame : AppFrame {
applySettings(_settings);
}
+ /// stop current program execution
+ void stopExecution() {
+ if (_execution) {
+ _execution.stop();
+ destroy(_execution);
+ _execution = null;
+ }
+ }
+
+ /// called when program execution is stopped
+ protected void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) {
+ executeInUiThread(delegate() {
+ Log.d("onProgramExecutionStatus process: ", process.executableFile, " status: ", status, " exitCode: ", exitCode);
+ _execution = null;
+ // TODO: update state
+ 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;
+ }
+ });
+ }
+
+ protected void runProject() {
+ 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));
+ return;
+ }
+ string[] args;
+ string externalConsoleExecutable = null; // TODO
+ string workingDirectory = null; // TODO
+ // TODO: provide thread safe listener
+ _logPanel.logLine("Starting " ~ executableFileName);
+ _execution = new ProgramExecutionNoDebug(executableFileName, args, workingDirectory, externalConsoleExecutable, this);
+ _execution.run();
+ // TODO: update status
+ }
+
override protected void init() {
_appName = "dlangide";
//_editorTool = new DEditorTool(this);
@@ -586,7 +649,8 @@ class IDEFrame : AppFrame {
case IDEActions.DebugStart:
case IDEActions.DebugStartNoDebug:
case IDEActions.DebugContinue:
- buildProject(BuildOperation.Run);
+ runProject();
+ //buildProject(BuildOperation.Run);
return true;
case IDEActions.UpdateProjectDependencies:
buildProject(BuildOperation.Upgrade);
@@ -980,6 +1044,7 @@ class IDEFrame : AppFrame {
/// called when main window is closing
void onWindowClose() {
Log.i("onWindowClose()");
+ stopExecution();
if (_dcdServer) {
if (_dcdServer.isRunning)
_dcdServer.stop();
diff --git a/src/dlangide/workspace/project.d b/src/dlangide/workspace/project.d
index c507d06..b57b6c2 100644
--- a/src/dlangide/workspace/project.d
+++ b/src/dlangide/workspace/project.d
@@ -438,6 +438,25 @@ class Project : WorkspaceItem {
return buildNormalizedPath(_filename.dirName, toUTF8(name) ~ WORKSPACE_EXTENSION);
}
+ @property bool isExecutable() {
+ // TODO: use targetType
+ return true;
+ }
+
+ /// return executable file name, or null if it's library project or executable is not found
+ @property string executableFileName() {
+ if (!isExecutable)
+ return null;
+ string exename = toUTF8(name);
+ // TODO: use targetName
+ version (Windows) {
+ exename = exename ~ ".exe";
+ }
+ // TODO: use targetPath
+ string exePath = buildNormalizedPath(_filename.dirName, "bin", exename);
+ return exePath;
+ }
+
ProjectFolder findItems(string[] srcPaths) {
ProjectFolder folder = new ProjectFolder(_filename);
folder.project = this;
diff --git a/workspaces/tetris/src/gui.d b/workspaces/tetris/src/gui.d
index 9a5ba39..8f11ae1 100644
--- a/workspaces/tetris/src/gui.d
+++ b/workspaces/tetris/src/gui.d
@@ -33,7 +33,7 @@ Widget createAboutWidget()
res.addChild(new TextWidget(null, "(C) Vadim Lopatin, 2014"d));
res.addChild(new TextWidget(null, "http://github.com/buggins/dlangui"d));
Button closeButton = new Button("close", "Close"d);
- closeButton.onClickListener = delegate(Widget src) {
+ closeButton.click = delegate(Widget src) {
Log.i("Closing window");
res.window.close();
return true;
@@ -521,7 +521,7 @@ class StatusWidget : VerticalLayout {
ImageWidget image = new ImageWidget(null, "tetris_logo_big");
image.layoutWidth(FILL_PARENT).alignment(Align.Center).clickable(true);
- image.onClickListener = delegate(Widget src) {
+ image.click = delegate(Widget src) {
_cup.handleAction(ACTION_PAUSE);
// about dialog when clicking on image
Window wnd = Platform.instance.createWindow("About...", window, WindowFlag.Modal);