1 module dlangide.ui.frame;
2 
3 import dlangui.widgets.menu;
4 import dlangui.widgets.tabs;
5 import dlangui.widgets.layouts;
6 import dlangui.widgets.editors;
7 import dlangui.widgets.srcedit;
8 import dlangui.widgets.controls;
9 import dlangui.widgets.appframe;
10 import dlangui.widgets.docks;
11 import dlangui.widgets.toolbars;
12 import dlangui.widgets.combobox;
13 import dlangui.widgets.popup;
14 import dlangui.dialogs.dialog;
15 import dlangui.dialogs.filedlg;
16 import dlangui.dialogs.settingsdialog;
17 import dlangui.core.stdaction;
18 import dlangui.core.files;
19 
20 import dlangide.ui.commands;
21 import dlangide.ui.wspanel;
22 import dlangide.ui.outputpanel;
23 import dlangide.ui.newfile;
24 import dlangide.ui.newproject;
25 import dlangide.ui.dsourceedit;
26 import dlangide.ui.homescreen;
27 import dlangide.ui.settings;
28 import dlangide.ui.debuggerui;
29 
30 import dlangide.workspace.workspace;
31 import dlangide.workspace.project;
32 import dlangide.builders.builder;
33 import dlangide.tools.editortool;
34 
35 import ddebug.common.execution;
36 import ddebug.common.nodebug;
37 import ddebug.common.debugger;
38 import ddebug.gdb.gdbinterface;
39 import dlangide.tools.d.dmdtrace;
40 
41 import std.conv;
42 import std.utf;
43 import std.algorithm : equal, endsWith;
44 import std.array : empty;
45 import std..string : split;
46 import std.path;
47 
48 // TODO: get version from GIT commit
49 //version is now stored in file views/VERSION
50 immutable dstring DLANGIDE_VERSION = toUTF32(import("VERSION"));
51 
52 bool isSupportedSourceTextFileFormat(string filename) {
53     return (filename.endsWith(".d") || filename.endsWith(".di") || filename.endsWith(".dt") || filename.endsWith(".txt") || filename.endsWith(".cpp") || filename.endsWith(".h") || filename.endsWith(".c")
54         || filename.endsWith(".json") || filename.endsWith(".sdl") || filename.endsWith(".dd") || filename.endsWith(".ddoc") || filename.endsWith(".xml") || filename.endsWith(".html")
55         || filename.endsWith(".html") || filename.endsWith(".css") || filename.endsWith(".log") || filename.endsWith(".hpp"));
56 }
57 
58 class BackgroundOperationWatcherTest : BackgroundOperationWatcher {
59     this(AppFrame frame) {
60         super(frame);
61     }
62     int _counter;
63     /// returns description of background operation to show in status line
64     override @property dstring description() { return "Test progress: "d ~ to!dstring(_counter); }
65     /// returns icon of background operation to show in status line
66     override @property string icon() { return "folder"; }
67     /// update background operation status
68     override void update() {
69         _counter++;
70         if (_counter >= 100)
71             _finished = true;
72         super.update();
73     }
74 }
75 
76 /// DIDE app frame
77 class IDEFrame : AppFrame, ProgramExecutionStatusListener, BreakpointListChangeListener, BookmarkListChangeListener {
78 
79     private ToolBarComboBox _projectConfigurationCombo;
80     
81     MenuItem mainMenuItems;
82     WorkspacePanel _wsPanel;
83     OutputPanel _logPanel;
84     DockHost _dockHost;
85     TabWidget _tabs;
86     // Is any workspace already opened?
87     private auto openedWorkspace = false;
88 
89     ///Cache for parsed D files for autocomplete and symbol finding
90     import dlangide.tools.d.dcdinterface;
91     private DCDInterface _dcdInterface;
92     @property DCDInterface dcdInterface() {
93         if (!_dcdInterface)
94             _dcdInterface = new DCDInterface();
95         return _dcdInterface; 
96     }
97 
98     IDESettings _settings;
99     ProgramExecution _execution;
100 
101     dstring frameWindowCaptionSuffix = "DLangIDE"d;
102 
103     this(Window window) {
104         super();
105         window.mainWidget = this;
106         window.onFilesDropped = &onFilesDropped;
107         window.onCanClose = &onCanClose;
108         window.onClose = &onWindowClose;
109         applySettings(_settings);
110     }
111 
112     ~this() {
113         if (_dcdInterface) {
114             destroy(_dcdInterface);
115             _dcdInterface = null;
116         }
117     }
118 
119     @property DockHost dockHost() { return _dockHost; }
120     @property OutputPanel logPanel() { return _logPanel; }
121 
122     /// stop current program execution
123     void stopExecution() {
124         if (_execution) {
125             _logPanel.logLine("Stopping program execution");
126             Log.d("Stopping execution");
127             _execution.stop();
128             //destroy(_execution);
129             _execution = null;
130         }
131     }
132 
133     /// returns true if program execution or debugging is active
134     @property bool isExecutionActive() {
135         return _execution !is null;
136     }
137     
138     /// Is any workspace already opened?
139     @property bool isOpenedWorkspace() {
140         return openedWorkspace;
141     }
142     
143     /// Is any workspace already opened?
144     @property void isOpenedWorkspace(bool opened) {
145         openedWorkspace = opened;    
146     }
147 
148     /// called when program execution is stopped
149     protected void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) {
150         executeInUiThread(delegate() {
151             Log.d("onProgramExecutionStatus process: ", process.executableFile, " status: ", status, " exitCode: ", exitCode);
152             _execution = null;
153             // TODO: update state
154             switch(status) {
155                 case ExecutionStatus.Error:
156                     _logPanel.logLine("Cannot run program " ~ process.executableFile);
157                     break;
158                 case ExecutionStatus.Finished:
159                     _logPanel.logLine("Program " ~ process.executableFile ~ " finished with exit code " ~ to!string(exitCode));
160                     break;
161                 case ExecutionStatus.Killed:
162                     _logPanel.logLine("Program " ~ process.executableFile ~ " is killed");
163                     break;
164                 default:
165                     _logPanel.logLine("Program " ~ process.executableFile ~ " is finished");
166                     break;
167             }
168             _statusLine.setBackgroundOperationStatus(null, null);
169         });
170     }
171 
172     protected void handleBuildError(int result, Project project) {
173         ErrorPosition err = _logPanel.firstError;
174         if (err) {
175             onCompilerLogIssueClick(err.projectname, err.filename, err.line, err.pos);
176         }
177     }
178 
179     protected void buildAndDebugProject(Project project) {
180         if (!currentWorkspace)
181             return;
182         if (!project)
183             project = currentWorkspace.startupProject;
184         if (!project) {
185             window.showMessageBox(UIString.fromId("ERROR_CANNOT_DEBUG_PROJECT"c), UIString.fromId("ERROR_STARTUP_PROJECT_ABSENT"c));
186             return;
187         }
188         buildProject(BuildOperation.Build, project, delegate(int result) {
189             if (!result) {
190                 Log.i("Build completed successfully. Starting debug for project.");
191                 debugProject(project);
192             } else {
193                 handleBuildError(result, project);
194             }
195         });
196     }
197 
198     void debugFinished(ProgramExecution process, ExecutionStatus status, int exitCode) {
199         _execution = null;
200         _debugHandler = null;
201         switch(status) {
202             case ExecutionStatus.Error:
203                 _logPanel.logLine("Cannot run program " ~ process.executableFile);
204                 _logPanel.activateLogTab();
205                 break;
206             case ExecutionStatus.Finished:
207                 _logPanel.logLine("Program " ~ process.executableFile ~ " finished with exit code " ~ to!string(exitCode));
208                 break;
209             case ExecutionStatus.Killed:
210                 _logPanel.logLine("Program " ~ process.executableFile ~ " is killed");
211                 break;
212             default:
213                 _logPanel.logLine("Program " ~ process.executableFile ~ " is finished");
214                 break;
215         }
216         _statusLine.setBackgroundOperationStatus(null, null);
217     }
218 
219     DebuggerUIHandler _debugHandler;
220     protected void debugProject(Project project) {
221         import std.file;
222         stopExecution();
223         if (!project) {
224             window.showMessageBox(UIString.fromId("ERROR_CANNOT_DEBUG_PROJECT"c), UIString.fromId("ERROR_STARTUP_PROJECT_ABSENT"c));
225             return;
226         }
227         string executableFileName = project.executableFileName;
228         if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) {
229             window.showMessageBox(UIString.fromId("ERROR_CANNOT_DEBUG_PROJECT"c), UIString.fromId("ERROR_CANNOT_FIND_EXEC"c));
230             return;
231         }
232         string debuggerExecutable = _settings.debuggerExecutable;
233         if (debuggerExecutable.empty) {
234             window.showMessageBox(UIString.fromId("ERROR_CANNOT_DEBUG_PROJECT"c), UIString.fromId("ERROR_NO_DEBUGGER"c));
235             return;
236         }
237 
238         GDBInterface program = new GDBInterface();
239         DebuggerProxy debuggerProxy = new DebuggerProxy(program, &executeInUiThread);
240         debuggerProxy.setDebuggerExecutable(debuggerExecutable);
241         setExecutableParameters(debuggerProxy, project, executableFileName);
242         _execution = debuggerProxy;
243         _debugHandler = new DebuggerUIHandler(this, debuggerProxy);
244         _debugHandler.onBreakpointListUpdated(currentWorkspace.getBreakpoints());
245         _debugHandler.run();
246     }
247 
248     protected void buildAndRunProject(Project project) {
249         if (!currentWorkspace)
250             return;
251         if (!project)
252             project = currentWorkspace.startupProject;
253         if (!project) {
254             window.showMessageBox(UIString.fromId("ERROR_CANNOT_RUN_PROJECT"c), UIString.fromId("ERROR_CANNOT_RUN_PROJECT"c));
255             return;
256         }
257         buildProject(BuildOperation.Build, project, delegate(int result) {
258             if (!result) {
259                 Log.i("Build completed successfully. Running program...");
260                 runProject(project);
261             } else {
262                 handleBuildError(result, project);
263             }
264         });
265     }
266 
267     protected void runProject(Project project) {
268         import std.file;
269         stopExecution();
270         if (!project) {
271             window.showMessageBox(UIString.fromId("ERROR_CANNOT_RUN_PROJECT"c), UIString.fromId("ERROR_STARTUP_PROJECT_ABSENT"c));
272             return;
273         }
274         string executableFileName = project.executableFileName;
275         if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) {
276             window.showMessageBox(UIString.fromId("ERROR_CANNOT_RUN_PROJECT"c), UIString.fromId("ERROR_CANNOT_FIND_EXEC"c));
277             return;
278         }
279         auto program = new ProgramExecutionNoDebug;
280         setExecutableParameters(program, project, executableFileName);
281         program.setProgramExecutionStatusListener(this);
282         _execution = program;
283         program.run();
284     }
285 
286     bool setExecutableParameters(ProgramExecution program, Project project, string executableFileName) {
287         string[] args;
288         string externalConsoleExecutable = null;
289         string workingDirectory = project.workingDirectory;
290         string tty = _logPanel.terminalDeviceName;
291         if (project.runInExternalConsole) {
292             version(Windows) {
293                 if (program.isMagoDebugger)
294                     tty = "external-console";
295             } else {
296                 externalConsoleExecutable = _settings.terminalExecutable;
297             }
298         }
299         if (!program.isDebugger)
300             _logPanel.logLine("MSG_STARTING"c ~ " " ~ executableFileName);
301         else
302             _logPanel.logLine("MSG_STARTING_DEBUGGER"c ~ " " ~ executableFileName);
303         const auto status =  program.isDebugger ?  UIString.fromId("DEBUGGING"c).value : UIString.fromId("RUNNING"c).value;
304         _statusLine.setBackgroundOperationStatus("debug-run", status);
305         string[string] env;
306         program.setExecutableParams(executableFileName, args, workingDirectory, env);
307         if (!tty.empty) {
308             Log.d("Terminal window device name: ", tty);
309             program.setTerminalTty(tty);
310             if (tty != "external-console")
311                 _logPanel.activateTerminalTab(true);
312         } else
313             program.setTerminalExecutable(externalConsoleExecutable);
314         return true;
315     }
316 
317     void runWithRdmd(string filename) {
318         stopExecution();
319 
320         string rdmdExecutable = _settings.rdmdExecutable;
321 
322         auto program = new ProgramExecutionNoDebug;
323         string sourceFileName = baseName(filename);
324         string workingDirectory = dirName(filename);
325         string[] args;
326         {
327             string rdmdAdditionalParams = _settings.rdmdAdditionalParams;
328             if (!rdmdAdditionalParams.empty)
329                 args ~= rdmdAdditionalParams.split();
330 
331             auto buildConfig = currentWorkspace ? currentWorkspace.buildConfiguration : BuildConfiguration.Debug;
332             switch (buildConfig) {
333                 default:
334                 case BuildConfiguration.Debug:
335                     args ~= "-debug";
336                     break;
337                 case BuildConfiguration.Release:
338                     args ~= "-release";
339                     break;
340                 case BuildConfiguration.Unittest:
341                     args ~= "-unittest";
342                     break;
343             }
344             args ~= sourceFileName;
345         }
346         string externalConsoleExecutable = null;
347         version(Windows) {
348         } else {
349             externalConsoleExecutable = _settings.terminalExecutable;
350         }
351         _logPanel.logLine("Starting " ~ sourceFileName ~ " with rdmd");
352         _statusLine.setBackgroundOperationStatus("run-rdmd", "running..."d);
353         program.setExecutableParams(rdmdExecutable, args, workingDirectory, null);
354         program.setTerminalExecutable(externalConsoleExecutable);
355         program.setProgramExecutionStatusListener(this);
356         _execution = program;
357         program.run();
358     }
359 
360     override protected void initialize() {
361         _appName = "dlangide";
362         //_editorTool = new DEditorTool(this);
363         _settings = new IDESettings(buildNormalizedPath(settingsDir, "settings.json"));
364         _settings.load();
365         _settings.updateDefaults();
366         _settings.save();
367         super.initialize();
368     }
369 
370     /// move focus to editor in currently selected tab
371     void focusEditor(string id) {
372         Widget w = _tabs.tabBody(id);
373         if (w) {
374             if (w.visible)
375                 w.setFocus();
376         }
377     }
378 
379     /// source file selected in workspace tree
380     bool onSourceFileSelected(ProjectSourceFile file, bool activate) {
381         Log.d("onSourceFileSelected ", file.filename, " activate=", activate);
382         if (activate)
383             return openSourceFile(file.filename, file, activate);
384         return false;
385     }
386 
387     /// returns global IDE settings
388     @property IDESettings settings() { return _settings; }
389 
390     ///
391     bool onCompilerLogIssueClick(dstring projectname, dstring filename, int line, int column)
392     {
393         Log.d("onCompilerLogIssueClick project=", projectname, " file=", filename, " line=", line, " column=", column);
394 
395         import std.conv:to;
396         string fname = to!string(filename);
397         //import std.path : isAbsolute;
398         ProjectSourceFile sourceFile = _wsPanel.findSourceFileItem(fname, isAbsolute(fname) ? true : false, projectname);
399         if (openSourceFile(fname, sourceFile)) {
400             Log.d("found source file");
401             if (sourceFile)
402                 _wsPanel.selectItem(sourceFile);
403             currentEditor().setCaretPos(line, 0);
404             currentEditor().setCaretPos(line, column);
405         }
406         return true;
407     }
408 
409     void onModifiedStateChange(Widget source, bool modified) {
410         //
411         Log.d("onModifiedStateChange ", source.id, " modified=", modified);
412         int index = _tabs.tabIndex(source.id);
413         if (index >= 0) {
414             dstring name = toUTF32((modified ? "* " : "") ~ baseName(source.id));
415             _tabs.renameTab(index, name);
416         }
417     }
418     
419     bool openSourceFile(string filename, ProjectSourceFile file = null, bool activate = true) {
420         if (!file && !filename)
421             return false;
422 
423         if (!file)
424             file = _wsPanel.findSourceFileItem(filename, false);
425 
426         //if(!file)
427         //    return false;
428 
429         if (file)
430             filename = file.filename;
431 
432         Log.d("openSourceFile ", filename);
433         int index = _tabs.tabIndex(filename);
434         if (index >= 0) {
435             // file is already opened in tab
436             _tabs.selectTab(index, true);
437         } else {
438             // open new file
439             DSourceEdit editor = new DSourceEdit(filename);
440             Log.d("trying to open source file ", filename);
441             if (file ? editor.load(file) : editor.load(filename)) {
442                 Log.d("file ", filename, " is opened ok");
443                 _tabs.addTab(editor, toUTF32(baseName(filename)), null, true, filename.toUTF32);
444                 index = _tabs.tabIndex(filename);
445                 TabItem tab = _tabs.tab(filename);
446                 tab.objectParam = file;
447                 editor.modifiedStateChange = &onModifiedStateChange;
448                 if (file) {
449                     editor.breakpointListChanged = this; //onBreakpointListChanged
450                     editor.bookmarkListChanged = this; //onBreakpointListChanged
451                     editor.setBreakpointList(currentWorkspace.getSourceFileBreakpoints(file));
452                     editor.setBookmarkList(currentWorkspace.getSourceFileBookmarks(file));
453                 }
454                 applySettings(editor, settings);
455                 _tabs.selectTab(index, true);
456                 if( filename.endsWith(".d") || filename.endsWith(".di") )
457                     editor.editorTool = new DEditorTool(this);
458                 else
459                     editor.editorTool = new DefaultEditorTool(this);
460                 _tabs.layout(_tabs.pos);
461                 editor.editorStateChange = _statusLine;
462             } else {
463                 Log.d("file ", filename, " cannot be opened");
464                 destroy(editor);
465                 if (window)
466                     window.showMessageBox(UIString.fromId("ERROR_OPEN_FILE"c), UIString.fromId("ERROR_OPENING_FILE"c) ~ " " ~ toUTF32(filename));
467                 return false;
468             }
469         }
470         if (activate) {
471             focusEditor(filename);
472         }
473         requestLayout();
474         return true;
475     }
476 
477     void showWorkspaceExplorer() {
478         _wsPanel.activate();
479     }
480 
481     static immutable HOME_SCREEN_ID = "HOME_SCREEN";
482     void showHomeScreen() {
483         int index = _tabs.tabIndex(HOME_SCREEN_ID);
484         if (index >= 0) {
485             _tabs.selectTab(index, true);
486         } else {
487             HomeScreen home = new HomeScreen(HOME_SCREEN_ID, this);
488             _tabs.addTab(home, UIString.fromId("HOME"c), null, true);
489             _tabs.selectTab(HOME_SCREEN_ID, true);
490              auto _settings = new IDESettings(buildNormalizedPath(settingsDir, "settings.json"));
491             // Auto open last workspace, if no workspace specified in command line and autoOpen flag set to true
492             const auto recentWorkspaces = settings.recentWorkspaces;
493             if (!openedWorkspace && recentWorkspaces.length > 0 && _settings.autoOpenLastProject())
494             {
495                 Action a = ACTION_FILE_OPEN_WORKSPACE.clone();
496                 a.stringParam = recentWorkspaces[0];
497                 handleAction(a);
498             }
499         }
500     }
501 
502     void hideHomeScreen() {
503         _tabs.removeTab(HOME_SCREEN_ID);
504     }
505 
506     void onTabChanged(string newActiveTabId, string previousTabId) {
507         int index = _tabs.tabIndex(newActiveTabId);
508         if (index >= 0) {
509             TabItem tab = _tabs.tab(index);
510             ProjectSourceFile file = cast(ProjectSourceFile)tab.objectParam;
511             if (file) {
512                 //setCurrentProject(file.project);
513                 // tab is source file editor
514                 _wsPanel.selectItem(file);
515                 focusEditor(file.filename);
516             }
517             //window.windowCaption(tab.text.value ~ " - "d ~ frameWindowCaptionSuffix);
518         } else {
519             //window.windowCaption(frameWindowCaptionSuffix);
520         }
521         requestActionsUpdate();
522     }
523 
524     // returns DSourceEdit from currently active tab (if it's editor), null if current tab is not editor or no tabs open
525     DSourceEdit currentEditor() {
526         return cast(DSourceEdit)_tabs.selectedTabBody();
527     }
528 
529     /// close tab w/o confirmation
530     void closeTab(string tabId) {
531         _wsPanel.selectItem(null);
532         _tabs.removeTab(tabId);
533         _statusLine.hideEditorState();
534         _tabs.focusSelectedTab();
535     }
536 
537     void renameTab(string oldfilename, string newfilename) {
538         int index = _tabs.tabIndex(newfilename);
539         if (index >= 0) {
540             // file is already opened in tab - close it
541             _tabs.removeTab(newfilename);
542         }
543         int oldindex = _tabs.tabIndex(oldfilename);
544         if (oldindex >= 0) {
545             _tabs.renameTab(oldindex, newfilename, UIString.fromRaw(newfilename.baseName));
546         }
547     }
548 
549     /// close all editor tabs
550     void closeAllDocuments() {
551         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
552             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
553             if (ed) {
554                 closeTab(ed.id);
555             }
556         }
557     }
558 
559     /// returns array of all opened source editors
560     DSourceEdit[] allOpenedEditors() {
561         DSourceEdit[] res;
562         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
563             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
564             if (ed) {
565                 res ~= ed;
566             }
567         }
568         return res;
569     }
570 
571     /// close editor tabs for which files are removed from filesystem
572     void closeRemovedDocuments() {
573         import std.file;
574         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
575             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
576             if (ed) {
577                 if (!exists(ed.id) || !isFile(ed.id)) {
578                     closeTab(ed.id);
579                 }
580             }
581         }
582     }
583 
584     /// returns first unsaved document
585     protected DSourceEdit hasUnsavedEdits() {
586         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
587             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
588             if (ed && ed.content.modified) {
589                 return ed;
590             }
591         }
592         return null;
593     }
594 
595     protected void askForUnsavedEdits(void delegate() onConfirm) {
596         DSourceEdit ed = hasUnsavedEdits();
597         if (!ed) {
598             // no unsaved edits
599             onConfirm();
600             return;
601         }
602         string tabId = ed.id;
603         // tab content is modified - ask for confirmation
604         auto header = UIString.fromId("HEADER_CLOSE_FILE"c);
605         window.showMessageBox(header ~ " " ~ toUTF32(baseName(tabId)), UIString.fromId("MSG_FILE_CONTENT_CHANGED"c), 
606                               [ACTION_SAVE, ACTION_SAVE_ALL, ACTION_DISCARD_CHANGES, ACTION_DISCARD_ALL, ACTION_CANCEL], 
607                               0, delegate(const Action result) {
608                                   if (result == StandardAction.Save) {
609                                       // save and close
610                                       ed.save();
611                                       askForUnsavedEdits(onConfirm);
612                                   } else if (result == StandardAction.DiscardChanges) {
613                                       // close, don't save
614                                       closeTab(tabId);
615                                       closeAllDocuments();
616                                       onConfirm();
617                                   } else if (result == StandardAction.SaveAll) {
618                                       ed.save();
619                                       for(;;) {
620                                           DSourceEdit editor = hasUnsavedEdits();
621                                           if (!editor)
622                                               break;
623                                           editor.save();
624                                       }
625                                       closeAllDocuments();
626                                       onConfirm();
627                                   } else if (result == StandardAction.DiscardAll) {
628                                       // close, don't save
629                                       closeAllDocuments();
630                                       onConfirm();
631                                   }
632                                   // else ignore
633                                   return true;
634                               });
635     }
636 
637     protected void onTabClose(string tabId) {
638         Log.d("onTabClose ", tabId);
639         int index = _tabs.tabIndex(tabId);
640         if (index >= 0) {
641             DSourceEdit d = cast(DSourceEdit)_tabs.tabBody(tabId);
642             if (d && d.content.modified) {
643                 // tab content is modified - ask for confirmation
644                 window.showMessageBox(UIString.fromId("HEADER_CLOSE_TAB"c), UIString.fromId("MSG_TAB_CONTENT_CHANGED"c) ~ ": " ~ toUTF32(baseName(tabId)), 
645                                       [ACTION_SAVE, ACTION_DISCARD_CHANGES, ACTION_CANCEL], 
646                                       0, delegate(const Action result) {
647                                           if (result == StandardAction.Save) {
648                                               // save and close
649                                               d.save();
650                                               closeTab(tabId);
651                                           } else if (result == StandardAction.DiscardChanges) {
652                                               // close, don't save
653                                               closeTab(tabId);
654                                           }
655                                           // else ignore
656                                           return true;
657                                       });
658             } else {
659                 closeTab(tabId);
660             }
661         }
662         requestActionsUpdate();
663     }
664 
665     /// create app body widget
666     override protected Widget createBody() {
667         _dockHost = new DockHost();
668 
669         //=============================================================
670         // Create body - Tabs
671 
672         // editor tabs
673         _tabs = new TabWidget("TABS");
674         _tabs.hiddenTabsVisibility = Visibility.Gone;
675         //_tabs.setStyles(STYLE_DOCK_HOST_BODY, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT);
676         _tabs.setStyles(STYLE_DOCK_WINDOW, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT, STYLE_DOCK_HOST_BODY);
677         _tabs.tabChanged = &onTabChanged;
678         _tabs.tabClose = &onTabClose;
679 
680         _dockHost.bodyWidget = _tabs;
681 
682         //=============================================================
683         // Create workspace docked panel
684         _wsPanel = new WorkspacePanel("workspace");
685         _wsPanel.sourceFileSelectionListener = &onSourceFileSelected;
686         _wsPanel.workspaceActionListener = &handleAction;
687         _wsPanel.dockAlignment = DockAlignment.Left;
688         _dockHost.addDockedWindow(_wsPanel);
689         _wsPanel.visibility = Visibility.Gone;
690 
691         _logPanel = new OutputPanel("output");
692         _logPanel.compilerLogIssueClickHandler = &onCompilerLogIssueClick;
693         _logPanel.appendText(null, "DlangIDE is started\nHINT: Try to open some DUB project\n"d);
694         dumpCompilerPaths();
695 
696         _dockHost.addDockedWindow(_logPanel);
697 
698         return _dockHost;
699     }
700 
701     private void dumpCompilerPaths() {
702         string dubPath = findExecutablePath("dub");
703         string rdmdPath = findExecutablePath("rdmd");
704         string dmdPath = findExecutablePath("dmd");
705         string ldcPath = findExecutablePath("ldc2");
706         string gdcPath = findExecutablePath("gdc");
707         _logPanel.appendText(null, dubPath ? ("dub path: "d ~ toUTF32(dubPath) ~ "\n"d) : ("dub is not found! cannot build projects without DUB\n"d));
708         _logPanel.appendText(null, rdmdPath ? ("rdmd path: "d ~ toUTF32(rdmdPath) ~ "\n"d) : ("rdmd is not found!\n"d));
709         _logPanel.appendText(null, dmdPath ? ("dmd path: "d ~ toUTF32(dmdPath) ~ "\n"d) : ("dmd compiler is not found!\n"d));
710         dumpCompilerPath("dmd", dmdPath);
711         _logPanel.appendText(null, ldcPath ? ("ldc path: "d ~ toUTF32(ldcPath) ~ "\n"d) : ("ldc compiler is not found!\n"d));
712         dumpCompilerPath("ldc", ldcPath);
713         _logPanel.appendText(null, gdcPath ? ("gdc path: "d ~ toUTF32(gdcPath) ~ "\n"d) : ("gdc compiler is not found!\n"d));
714         dumpCompilerPath("gdc", gdcPath);
715     }
716     private void dumpCompilerPath(string compilerName, string compiler) {
717         if (!compiler)
718             return;
719         if (compiler) {
720             string[] imports = compilerImportPathsCache.getImportPathsFor(compilerName);
721             if (imports.length > 0) {
722                 Log.d(compilerName, " imports:", imports);
723                 _logPanel.appendText(null, to!dstring(compilerName) ~ " imports:\n"d);
724                 foreach(s; imports)
725                     _logPanel.appendText(null, "    "d ~ to!dstring(s) ~ "\n"d);
726             }
727         }
728     }
729 
730     private MenuItem _projectConfigurationMenuItem;
731     /// create main menu
732     override protected MainMenu createMainMenu() {
733 
734         mainMenuItems = new MenuItem();
735         MenuItem fileItem = new MenuItem(new Action(1, "MENU_FILE"));
736         MenuItem fileNewItem = new MenuItem(new Action(1, "MENU_FILE_NEW"));
737         fileNewItem.add(ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT);
738         fileItem.add(fileNewItem);
739         fileItem.add(ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 
740                      ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_WORKSPACE_CLOSE, ACTION_FILE_EXIT);
741 
742         MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT"));
743         editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, 
744                      ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO);
745         editItem.addSeparator();
746         editItem.add(ACTION_EDITOR_FIND, ACTION_EDITOR_FIND_NEXT, ACTION_EDITOR_FIND_PREV, ACTION_EDITOR_REPLACE, ACTION_FIND_TEXT, ACTION_EDITOR_TOGGLE_BOOKMARK);
747         editItem.addSeparator();
748         MenuItem editItemAdvanced = new MenuItem(new Action(221, "MENU_EDIT_ADVANCED"));
749         editItemAdvanced.add(ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT);
750         editItem.add(editItemAdvanced);
751         editItem.add(ACTION_EDIT_PREFERENCES);
752 
753         MenuItem viewItem = new MenuItem(new Action(3, "MENU_VIEW"));
754         viewItem.add(ACTION_WINDOW_SHOW_HOME_SCREEN, ACTION_WINDOW_SHOW_WORKSPACE_EXPLORER, ACTION_WINDOW_SHOW_LOG_WINDOW);
755         viewItem.addSeparator();
756         viewItem.addCheck(ACTION_VIEW_TOGGLE_TOOLBAR);
757         viewItem.addCheck(ACTION_VIEW_TOGGLE_STATUSBAR);
758         viewItem.addSeparator();
759         viewItem.addCheck(ACTION_VIEW_TOGGLE_SHOW_WHITESPACES);
760         viewItem.addCheck(ACTION_VIEW_TOGGLE_TAB_POSITIONS);
761 
762         MenuItem navItem = new MenuItem(new Action(21, "MENU_NAVIGATE"));
763         navItem.add(ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS, ACTION_GET_DOC_COMMENTS, 
764             ACTION_GET_PAREN_COMPLETION, ACTION_EDITOR_GOTO_PREVIOUS_BOOKMARK, 
765             ACTION_EDITOR_GOTO_NEXT_BOOKMARK, ACTION_GO_TO_LINE);
766 
767         MenuItem projectItem = new MenuItem(new Action(21, "MENU_PROJECT"));
768         projectItem.add(ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, ACTION_PROJECT_SETTINGS);
769 
770         MenuItem buildItem = new MenuItem(new Action(22, "MENU_BUILD"));
771         Action configs = ACTION_PROJECT_BUILD_CONFIGURATION.clone;
772         configs.longParam = -1;
773         MenuItem buildConfiguration = new MenuItem(configs);
774         foreach (config; BuildConfiguration.min .. BuildConfiguration.max + 1) {
775             Action a = ACTION_PROJECT_BUILD_CONFIGURATION.clone;
776             a.label = ["Debug"d,"Release"d,"Unittest"d][config];
777             a.longParam = config;
778             MenuItem child = new MenuItem(a);
779             child.type = MenuItemType.Radio;
780             buildConfiguration.add(child);
781         }
782         buildItem.add(buildConfiguration);
783 
784         _projectConfigurationMenuItem = new MenuItem(ACTION_PROJECT_CONFIGURATION);
785         Action defaultConfigAction = ACTION_PROJECT_CONFIGURATION.clone;
786         defaultConfigAction.label = "default"d;
787         defaultConfigAction.stringParam = "default";
788         MenuItem defaultConfigItem = new MenuItem(defaultConfigAction);
789         defaultConfigItem.type = MenuItemType.Radio;
790         buildConfiguration.add(defaultConfigItem);
791         buildItem.add(_projectConfigurationMenuItem);
792         buildItem.addSeparator();
793 
794         buildItem.add(ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
795                       ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN,
796                       ACTION_RUN_WITH_RDMD);
797 
798         MenuItem debugItem = new MenuItem(new Action(23, "MENU_DEBUG"));
799         debugItem.add(ACTION_DEBUG_START, ACTION_DEBUG_START_NO_DEBUG,
800                       ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE,
801                       ACTION_DEBUG_RESTART,
802                       ACTION_DEBUG_STEP_INTO,
803                       ACTION_DEBUG_STEP_OVER,
804                       ACTION_DEBUG_STEP_OUT,
805                       ACTION_DEBUG_TOGGLE_BREAKPOINT, ACTION_DEBUG_ENABLE_BREAKPOINT, ACTION_DEBUG_DISABLE_BREAKPOINT
806                       );
807 
808 
809         MenuItem toolsItem = new MenuItem(new Action(33, "MENU_TOOLS"c));
810         toolsItem.add(ACTION_TOOLS_OPEN_DMD_TRACE_LOG);
811 
812         MenuItem windowItem = new MenuItem(new Action(3, "MENU_WINDOW"c));
813         //windowItem.add(new Action(30, "MENU_WINDOW_PREFERENCES"));
814         windowItem.add(ACTION_WINDOW_CLOSE_DOCUMENT, ACTION_WINDOW_CLOSE_ALL_DOCUMENTS);
815 
816         MenuItem helpItem = new MenuItem(new Action(4, "MENU_HELP"c));
817         helpItem.add(ACTION_HELP_VIEW_HELP, ACTION_HELP_ABOUT, ACTION_HELP_DONATE);
818         mainMenuItems.add(fileItem);
819         mainMenuItems.add(editItem);
820         mainMenuItems.add(viewItem);
821         mainMenuItems.add(projectItem);
822         mainMenuItems.add(navItem);
823         mainMenuItems.add(buildItem);
824         mainMenuItems.add(debugItem);
825         mainMenuItems.add(toolsItem);
826         //mainMenuItems.add(viewItem);
827         mainMenuItems.add(windowItem);
828         mainMenuItems.add(helpItem);
829 
830         MainMenu mainMenu = new MainMenu(mainMenuItems);
831         //mainMenu.backgroundColor = 0xd6dbe9;
832         return mainMenu;
833     }
834 
835     /// override it
836     override protected void updateShortcuts() {
837         if (applyShortcutsSettings()) {
838             Log.d("Shortcut actions loaded");
839         } else {
840             Log.d("Saving default shortcuts");
841             const(Action)[] actions;
842             actions ~= STD_IDE_ACTIONS;
843             actions ~= STD_EDITOR_ACTIONS;
844             saveShortcutsSettings(actions);
845         }
846     }
847 
848     private ToolBarComboBox _cbBuildConfiguration;
849     /// create app toolbars
850     override protected ToolBarHost createToolbars() {
851         ToolBarHost res = new ToolBarHost();
852         ToolBar tb;
853         tb = res.getOrAddToolbar("Standard");
854         tb.addButtons(ACTION_FILE_OPEN, ACTION_FILE_SAVE, ACTION_SEPARATOR);
855 
856         tb.addButtons(ACTION_DEBUG_START);
857         
858         _projectConfigurationCombo = new ToolBarComboBox("projectConfig", [ProjectConfiguration.DEFAULT_NAME.to!dstring]);//Updateable
859         _projectConfigurationCombo.action = ACTION_PROJECT_CONFIGURATIONS;
860         tb.addControl(_projectConfigurationCombo);
861         
862         _cbBuildConfiguration = new ToolBarComboBox("buildConfig", ["Debug"d, "Release"d, "Unittest"d]);
863         _cbBuildConfiguration.itemClick = delegate(Widget source, int index) {
864             if (currentWorkspace && index < 3) {
865                 currentWorkspace.buildConfiguration = [BuildConfiguration.Debug, BuildConfiguration.Release, BuildConfiguration.Unittest][index];
866             }
867             return true;
868         };
869         _cbBuildConfiguration.action = ACTION_BUILD_CONFIGURATIONS;
870         tb.addControl(_cbBuildConfiguration);
871 
872         tb.addButtons(ACTION_PROJECT_BUILD, ACTION_SEPARATOR, ACTION_RUN_WITH_RDMD);
873 
874         tb = res.getOrAddToolbar("Edit");
875         tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR,
876                       ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT);
877         tb = res.getOrAddToolbar("Debug");
878         tb.addButtons(ACTION_DEBUG_STOP, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_PAUSE,
879                       ACTION_DEBUG_RESTART,
880                       ACTION_DEBUG_STEP_INTO,
881                       ACTION_DEBUG_STEP_OVER,
882                       ACTION_DEBUG_STEP_OUT,
883                       );
884         return res;
885     }
886 
887     /// override to handle specific actions state (e.g. change enabled state for supported actions)
888     override bool handleActionStateRequest(const Action a) {
889         switch (a.id) {
890             case IDEActions.EditPreferences:
891                 return true;
892             case IDEActions.WindowShowWorkspaceExplorer:
893                 a.state = currentWorkspace !is null ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
894                 return true;
895             case IDEActions.FileExit:
896             case IDEActions.FileOpen:
897             case IDEActions.WindowShowHomeScreen:
898             case IDEActions.FileOpenWorkspace:
899                 // disable when background operation in progress
900                 if (!_currentBackgroundOperation)
901                     a.state = ACTION_STATE_ENABLED;
902                 else
903                     a.state = ACTION_STATE_DISABLE;
904                 return true;
905             case IDEActions.FileNew:
906                 a.state = (currentWorkspace && currentWorkspace.startupProject) ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
907                 return true;
908             case IDEActions.HelpAbout:
909             case StandardAction.OpenUrl:
910                 // always enabled
911                 a.state = ACTION_STATE_ENABLED;
912                 return true;
913             case IDEActions.BuildProject:
914             case IDEActions.BuildWorkspace:
915             case IDEActions.RebuildProject:
916             case IDEActions.RebuildWorkspace:
917             case IDEActions.CleanProject:
918             case IDEActions.CleanWorkspace:
919             case IDEActions.UpdateProjectDependencies:
920             case IDEActions.RefreshProject:
921             case IDEActions.SetStartupProject:
922             case IDEActions.ProjectSettings:
923             case IDEActions.RevealProjectInExplorer:
924                 // enable when project exists
925                 if (currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
926                     a.state = ACTION_STATE_ENABLED;
927                 else
928                     a.state = ACTION_STATE_DISABLE;
929                 return true;
930             case IDEActions.BuildSetConfiguration:
931                 // enable when project exists
932                 if (currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
933                     a.state = currentWorkspace.buildConfiguration == a.longParam ? ACTION_STATE_CHECKED : ACTION_STATE_ENABLED;
934                 else
935                     a.state = ACTION_STATE_DISABLE;
936                 return true;
937             case IDEActions.ProjectSetConfiguration:
938                 // enable when project exists
939                 if (currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
940                     a.state = currentWorkspace.startupProject.projectConfiguration.name == a.stringParam ? ACTION_STATE_CHECKED : ACTION_STATE_ENABLED;
941                 else
942                     a.state = ACTION_STATE_DISABLE;
943                 return true;
944             case IDEActions.RunWithRdmd:
945                 // enable when D source file is in current tab
946                 if (currentEditor && !_currentBackgroundOperation && currentEditor.id.endsWith(".d"))
947                     a.state = ACTION_STATE_ENABLED;
948                 else
949                     a.state = ACTION_STATE_DISABLE;
950                 return true;
951             case IDEActions.DebugStop:
952                 a.state = isExecutionActive ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
953                 return true;
954             case IDEActions.DebugStart:
955             case IDEActions.DebugStartNoDebug:
956                 if (!isExecutionActive && currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
957                     a.state = ACTION_STATE_ENABLED;
958                 else
959                     a.state = ACTION_STATE_DISABLE;
960                 return true;
961             case IDEActions.DebugContinue:
962             case IDEActions.DebugPause:
963             case IDEActions.DebugStepInto:
964             case IDEActions.DebugStepOver:
965             case IDEActions.DebugStepOut:
966             case IDEActions.DebugRestart:
967                 if (_debugHandler)
968                     return _debugHandler.handleActionStateRequest(a);
969                 else
970                     a.state = ACTION_STATE_DISABLE;
971                 return true;
972             case IDEActions.FindInFiles:
973                 a.state = currentWorkspace !is null ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
974                 return true;
975             case IDEActions.CloseWorkspace:
976                 a.state = (currentWorkspace !is null && !_currentBackgroundOperation) ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
977                 return true;
978             case IDEActions.WindowCloseDocument:
979             case IDEActions.WindowCloseAllDocuments:
980             case IDEActions.FileSaveAll:
981             case IDEActions.FileSaveAs:
982             case IDEActions.GotoLine:
983             case EditorActions.Find:
984             case EditorActions.FindNext:
985             case EditorActions.FindPrev:
986             case EditorActions.Replace:
987                 a.state = (currentEditor !is null && !_currentBackgroundOperation) ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
988                 return true;
989             case IDEActions.ViewToggleWhitespaceMarks:
990                 a.state = _settings.showWhiteSpaceMarks ? ACTION_STATE_CHECKED : ACTION_STATE_ENABLED;
991                 return true;
992             case IDEActions.ViewToggleTabPositionMarks:
993                 a.state = _settings.showTabPositionMarks ? ACTION_STATE_CHECKED : ACTION_STATE_ENABLED;
994                 return true;
995             case IDEActions.ViewToggleToolbar:
996                 a.state = _settings.showToolbar ? ACTION_STATE_CHECKED : ACTION_STATE_ENABLED;
997                 return true;
998             case IDEActions.ViewToggleStatusbar:
999                 a.state = _settings.showStatusbar ? ACTION_STATE_CHECKED : ACTION_STATE_ENABLED;
1000                 return true;
1001             case IDEActions.ProjectFolderExpandAll:
1002             case IDEActions.ProjectFolderCollapseAll:
1003                 a.state = currentWorkspace !is null ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
1004                 return true;
1005             default:
1006                 return super.handleActionStateRequest(a);
1007         }
1008     }
1009 
1010     static immutable TRACE_LOG_ID = "TRACE_LOG";
1011     void showDMDTraceLog(DMDTraceLogParser data) {
1012         import dlangide.ui.dmdprofilerview;
1013         int index = _tabs.tabIndex(TRACE_LOG_ID);
1014         if (index >= 0) {
1015             _tabs.removeTab(TRACE_LOG_ID);
1016         }
1017         DMDProfilerView home = new DMDProfilerView(TRACE_LOG_ID, this, data);
1018         _tabs.addTab(home, UIString.fromId("PROFILER_WINDOW"c), null, true);
1019         _tabs.selectTab(TRACE_LOG_ID, true);
1020     }
1021 
1022     //void showDMDTraceLog()
1023     void openDMDTraceLog(string filename) {
1024         DMDProfilerLogParserOperation op = new DMDProfilerLogParserOperation(this, filename, _logPanel,
1025             delegate(DMDTraceLogParser parser) {
1026                 if (parser) {
1027                     Log.d("Trace log is ready");
1028                     showDMDTraceLog(parser);
1029                 } else {
1030                     Log.e("Trace log is failed");
1031                     window.showMessageBox(UIString.fromId("ERROR"c), UIString.fromId("ERROR_FAILED_TO_PARSE_FILE"c));
1032                 }
1033             }
1034         );
1035         setBackgroundOperation(op);
1036     }
1037 
1038     void openDMDTraceLog() {
1039         UIString caption;
1040         caption = UIString.fromId("HEADER_OPEN_DMD_PROFILER_LOG"c);
1041         FileDialog dlg = createFileDialog(caption);
1042         dlg.addFilter(FileFilterEntry(UIString.fromId("PROFILER_LOG_FILES"c), "*.log"));
1043         dlg.path = _settings.getRecentPath("FILE_OPEN_PATH");
1044         dlg.dialogResult = delegate(Dialog d, const Action result) {
1045             if (result.id == ACTION_OPEN.id) {
1046                 string filename = result.stringParam;
1047                 _settings.setRecentPath(dlg.path, "FILE_OPEN_PATH");
1048                 openDMDTraceLog(filename);
1049             }
1050         };
1051         dlg.show();
1052     }
1053 
1054     FileDialog createFileDialog(UIString caption, int fileDialogFlags = DialogFlag.Modal | DialogFlag.Resizable | FileDialogFlag.FileMustExist) {
1055         FileDialog dlg = new FileDialog(caption, window, null, fileDialogFlags);
1056         dlg.filetypeIcons[".d"] = "text-d";
1057         dlg.filetypeIcons["dub.json"] = "project-d";
1058         dlg.filetypeIcons["dub.sdl"] = "project-d";
1059         dlg.filetypeIcons["package.json"] = "project-d";
1060         dlg.filetypeIcons[".dlangidews"] = "project-development";
1061         return dlg;
1062     }
1063 
1064     /// override to handle specific actions
1065     override bool handleAction(const Action a) {
1066         if (a) {
1067             switch (a.id) {
1068                 case IDEActions.FileExit:
1069                     if (onCanClose())
1070                         window.close();
1071                     return true;
1072                 case IDEActions.HelpViewHelp:
1073                     Platform.instance.openURL(HELP_PAGE_URL);
1074                     return true;
1075                 case IDEActions.HelpDonate:
1076                     Platform.instance.openURL(HELP_DONATION_URL);
1077                     return true;
1078                 case IDEActions.ToolsOpenDMDTraceLog:
1079                     openDMDTraceLog();
1080                     return true;
1081                 case IDEActions.HelpAbout:
1082                     //debug {
1083                     //    testDCDFailAfterThreadCreation();
1084                     //}
1085                     dstring msg = "DLangIDE\n(C) Vadim Lopatin, 2014-2017\nhttp://github.com/buggins/dlangide\n" 
1086                         ~ "IDE for D programming language written in D\nUses DlangUI library " 
1087                         ~ DLANGUI_VERSION ~ " for GUI"d;
1088                     window.showMessageBox(UIString.fromId("ABOUT"c) ~ " " ~ DLANGIDE_VERSION,
1089                                           UIString.fromRaw(msg));
1090                     return true;
1091                 case IDEActions.BuildSetConfiguration:
1092                     // set build configuration
1093                     if (currentWorkspace && a.longParam >= BuildConfiguration.min && a.longParam <= BuildConfiguration.max) {
1094                         if (currentWorkspace.buildConfiguration != a.longParam) {
1095                             currentWorkspace.buildConfiguration = cast(BuildConfiguration)a.longParam;
1096                             Log.d("Changing build configuration to ", currentWorkspace.buildConfiguration);
1097                             _cbBuildConfiguration.selectedItemIndex = currentWorkspace.buildConfiguration;
1098                         }
1099                     }
1100                     return true;
1101                 case IDEActions.ProjectSetConfiguration:
1102                     if (currentWorkspace && currentWorkspace.startupProject && a.stringParam) {
1103                         currentWorkspace.startupProject.projectConfiguration = a.stringParam;
1104                         updateProjectConfigurations();
1105                     }
1106                     return true;
1107                 case IDEActions.ProjectFolderOpenItem:
1108                     ProjectItem item = cast(ProjectItem)a.objectParam;
1109                     if (item && !item.isFolder) {
1110                         openSourceFile(item.filename);
1111                     }
1112                     return true;
1113                 case StandardAction.OpenUrl:
1114                     platform.openURL(a.stringParam);
1115                     return true;
1116                 case IDEActions.FileSaveAs:
1117                     DSourceEdit ed = currentEditor;
1118                     UIString caption;
1119                     caption = UIString.fromId("HEADER_SAVE_FILE_AS"c);
1120                     FileDialog dlg = createFileDialog(caption, DialogFlag.Modal | DialogFlag.Resizable | FileDialogFlag.Save);
1121                     dlg.addFilter(FileFilterEntry(UIString.fromId("SOURCE_FILES"c), "*.d;*.dd;*.ddoc;*.di;*.dt;*.dh;*.json;*.sdl;*.xml;*.ini"));
1122                     dlg.addFilter(FileFilterEntry(UIString.fromId("ALL_FILES"c), "*.*"));
1123                     dlg.path = ed.filename.dirName;
1124                     dlg.filename = ed.filename;
1125                     dlg.dialogResult = delegate(Dialog d, const Action result) {
1126                         if (result.id == ACTION_SAVE.id) {
1127                             string oldfilename = ed.filename;
1128                             string filename = result.stringParam;
1129                             ed.save(filename);
1130                             if (oldfilename == filename)
1131                                 return;
1132                             renameTab(oldfilename, filename);
1133                             ed.id = filename;
1134                             ed.setSyntaxSupport();
1135                             if( filename.endsWith(".d") || filename.endsWith(".di") )
1136                                 ed.editorTool = new DEditorTool(this);
1137                             else
1138                                 ed.editorTool = new DefaultEditorTool(this);
1139                             //openSourceFile(filename);
1140                             refreshWorkspace();
1141                             ProjectSourceFile file = _wsPanel.findSourceFileItem(filename, false);
1142                             if (file) {
1143                                 ed.projectSourceFile = file;
1144                             } else
1145                                 ed.projectSourceFile = null;
1146                             _settings.setRecentPath(dlg.path, "FILE_OPEN_PATH");
1147                         }
1148                     };
1149                     dlg.show();
1150                     return true;
1151                 case IDEActions.FileOpen:
1152                     UIString caption;
1153                     caption = UIString.fromId("HEADER_OPEN_TEXT_FILE"c);
1154                     FileDialog dlg = createFileDialog(caption);
1155                     dlg.addFilter(FileFilterEntry(UIString.fromId("SOURCE_FILES"c), "*.d;*.dd;*.ddoc;*.di;*.dt;*.dh;*.json;*.sdl;*.xml;*.ini"));
1156                     dlg.addFilter(FileFilterEntry(UIString.fromId("ALL_FILES"c), "*.*"));
1157                     dlg.path = _settings.getRecentPath("FILE_OPEN_PATH");
1158                     dlg.dialogResult = delegate(Dialog d, const Action result) {
1159                         if (result.id == ACTION_OPEN.id) {
1160                             string filename = result.stringParam;
1161                             openSourceFile(filename);
1162                             _settings.setRecentPath(dlg.path, "FILE_OPEN_PATH");
1163                         }
1164                     };
1165                     dlg.show();
1166                     return true;
1167                 case IDEActions.BuildProject:
1168                 case IDEActions.BuildWorkspace:
1169                     buildProject(BuildOperation.Build, cast(Project)a.objectParam);
1170                     return true;
1171                 case IDEActions.RebuildProject:
1172                 case IDEActions.RebuildWorkspace:
1173                     buildProject(BuildOperation.Rebuild, cast(Project)a.objectParam);
1174                     return true;
1175                 case IDEActions.CleanProject:
1176                 case IDEActions.CleanWorkspace:
1177                     buildProject(BuildOperation.Clean, cast(Project)a.objectParam);
1178                     return true;
1179                 case IDEActions.RunWithRdmd:
1180                     runWithRdmd(currentEditor.id);
1181                     return true;
1182                 case IDEActions.DebugStartNoDebug:
1183                     buildAndRunProject(cast(Project)a.objectParam);
1184                     return true;
1185                 case IDEActions.DebugStart:
1186                     buildAndDebugProject(cast(Project)a.objectParam);
1187                     return true;
1188                 case IDEActions.DebugPause:
1189                 case IDEActions.DebugStepInto:
1190                 case IDEActions.DebugStepOver:
1191                 case IDEActions.DebugStepOut:
1192                 case IDEActions.DebugRestart:
1193                     if (_debugHandler)
1194                         return _debugHandler.handleAction(a);
1195                     return true;
1196                 case IDEActions.DebugContinue:
1197                     if (_debugHandler)
1198                         return _debugHandler.handleAction(a);
1199                     else
1200                         buildAndRunProject(cast(Project)a.objectParam);
1201                     return true;
1202                 case IDEActions.DebugStop:
1203                     if (_debugHandler)
1204                         return _debugHandler.handleAction(a);
1205                     else
1206                         stopExecution();
1207                     return true;
1208                 case IDEActions.UpdateProjectDependencies:
1209                     buildProject(BuildOperation.Upgrade, cast(Project)a.objectParam);
1210                     return true;
1211                 case IDEActions.RefreshProject:
1212                     refreshWorkspace();
1213                     return true;
1214                 case IDEActions.RevealProjectInExplorer:
1215                     revealProjectInExplorer(cast(Project)a.objectParam);
1216                     return true;
1217                 case IDEActions.WindowCloseDocument:
1218                     onTabClose(_tabs.selectedTabId);
1219                     return true;
1220                 case IDEActions.WindowCloseAllDocuments:
1221                     askForUnsavedEdits(delegate() {
1222                         closeAllDocuments();
1223                     });
1224                     return true;
1225                 case IDEActions.WindowShowHomeScreen:
1226                     showHomeScreen();
1227                     return true;
1228                 case IDEActions.WindowShowWorkspaceExplorer:
1229                     showWorkspaceExplorer();
1230                     return true;
1231                 case IDEActions.WindowShowLogWindow:
1232                     _logPanel.activateLogTab();
1233                     return true;
1234                 case IDEActions.ViewToggleWhitespaceMarks:
1235                     _settings.showWhiteSpaceMarks = !_settings.showWhiteSpaceMarks;
1236                     _settings.save();
1237                     applySettings(_settings);
1238                     return true;
1239                 case IDEActions.ViewToggleTabPositionMarks:
1240                     _settings.showTabPositionMarks = !_settings.showTabPositionMarks;
1241                     _settings.save();
1242                     applySettings(_settings);
1243                     return true;
1244                 case IDEActions.ViewToggleToolbar:
1245                     _settings.showToolbar = !_settings.showToolbar;
1246                     _settings.save();
1247                     applySettings(_settings);
1248                     return true;
1249                 case IDEActions.ViewToggleStatusbar:
1250                     _settings.showStatusbar = !_settings.showStatusbar;
1251                     _settings.save();
1252                     applySettings(_settings);
1253                     return true;
1254                 case IDEActions.FileOpenWorkspace:
1255                     // Already specified workspace
1256                     if (!a.stringParam.empty) {
1257                         openFileOrWorkspace(a.stringParam);
1258                         return true;
1259                     }
1260                     // Ask user for workspace to open
1261                     UIString caption = UIString.fromId("HEADER_OPEN_WORKSPACE_OR_PROJECT"c);
1262                     FileDialog dlg = createFileDialog(caption);
1263                     dlg.addFilter(FileFilterEntry(UIString.fromId("WORKSPACE_AND_PROJECT_FILES"c), "*.dlangidews;dub.json;dub.sdl;package.json"));
1264                     dlg.path = _settings.getRecentPath("FILE_OPEN_WORKSPACE_PATH");
1265                     dlg.dialogResult = delegate(Dialog d, const Action result) {
1266                         if (result.id == ACTION_OPEN.id) {
1267                             string filename = result.stringParam;
1268                             if (filename.length) {
1269                                 openFileOrWorkspace(filename);
1270                                 _settings.setRecentPath(dlg.path, "FILE_OPEN_WORKSPACE_PATH");
1271                             }
1272                         }
1273                     };
1274                     dlg.show();
1275                     return true;
1276                 case IDEActions.GoToDefinition:
1277                     if (currentEditor) {
1278                         Log.d("Trying to go to definition.");
1279                         currentEditor.editorTool.goToDefinition(currentEditor(), currentEditor.caretPos);
1280                     }
1281                     return true;
1282                 case IDEActions.GotoLine:
1283                     // Go to line without editor is meaningless command
1284                     if (currentEditor) {
1285                         Log.d("Go to line");
1286                         // Ask user for line
1287                         window.showInputBox(UIString.fromId("GO_TO_LINE"c), UIString.fromId("GO_TO_LINE"c), ""d, delegate(dstring s) {
1288                             try {
1289                                 auto num = to!uint(s);
1290                                 // Check line existence
1291                                 if (num < 1 || num > currentEditor.content.length) {
1292                                     currentEditor.setFocus();
1293                                     window.showMessageBox(UIString.fromId("ERROR"c), UIString.fromId("ERROR_NO_SUCH_LINE"c));
1294                                     return;
1295                                 }
1296                                 // Go to line
1297                                 currentEditor.setCaretPos(num - 1, 0);
1298                                 currentEditor.setFocus();
1299                             }
1300                             catch (ConvException e) {
1301                                 currentEditor.setFocus();
1302                                 window.showMessageBox(UIString.fromId("ERROR"c), UIString.fromId("ERROR_INVALID_NUMBER"c));
1303                             }
1304                         });
1305                     }
1306                     return true;
1307                 case IDEActions.GetDocComments:
1308                     Log.d("Trying to get doc comments.");
1309                     currentEditor.editorTool.getDocComments(currentEditor, currentEditor.caretPos, delegate(string[] results) {
1310                         if (results.length)
1311                             currentEditor.showDocCommentsPopup(results);
1312                     });
1313                     return true;
1314                 case IDEActions.GetParenCompletion:
1315                     Log.d("Trying to get paren completion.");
1316                     //auto results = currentEditor.editorTool.getParenCompletion(currentEditor, currentEditor.caretPos);
1317                     return true;
1318                 case IDEActions.GetCompletionSuggestions:
1319                     Log.d("Getting auto completion suggestions.");
1320                     currentEditor.editorTool.getCompletions(currentEditor, currentEditor.caretPos, delegate(dstring[] results, string[] icons, CompletionTypes type) {
1321                         if (currentEditor)
1322                             currentEditor.showCompletionPopup(results, icons, type);
1323                     });
1324                     return true;
1325                 case IDEActions.EditPreferences:
1326                     showPreferences();
1327                     return true;
1328                 case IDEActions.ProjectSettings:
1329                     showProjectSettings(cast(Project)a.objectParam);
1330                     return true;
1331                 case IDEActions.SetStartupProject:
1332                     setStartupProject(cast(Project)a.objectParam);
1333                     return true;
1334                 case IDEActions.FindInFiles:
1335                     Log.d("Opening Search In Files panel");
1336                     if (!currentWorkspace) {
1337                         Log.d("No workspace is opened");
1338                         return true;
1339                     }
1340                     import dlangide.ui.searchPanel;
1341                     _logPanel.ensureLogVisible();
1342                     int searchPanelIndex = _logPanel.getTabs.tabIndex("search");
1343                     SearchWidget searchPanel = null;
1344                     if(searchPanelIndex == -1) {
1345                         searchPanel = new SearchWidget("search", this);
1346                         _logPanel.getTabs.addTab( searchPanel, "Search"d, null, true);
1347                     }
1348                     else {
1349                         searchPanel = cast(SearchWidget) _logPanel.getTabs.tabBody(searchPanelIndex);
1350                     }
1351                     _logPanel.getTabs.selectTab("search");
1352                     if(searchPanel !is null) { 
1353                         searchPanel.focus();
1354                         dstring selectedText;
1355                         if (currentEditor)
1356                             selectedText = currentEditor.getSelectedText();
1357                         searchPanel.setSearchText(selectedText);
1358                         searchPanel.checkSearchMode();
1359                     }
1360                     return true;
1361                 case IDEActions.FileNewWorkspace:
1362                     createNewProject(true);
1363                     return true;
1364                 case IDEActions.FileNewProject:
1365                     createNewProject(false);
1366                     return true;
1367                 case IDEActions.FileNew:
1368                     addProjectItem(cast(Object)a.objectParam);
1369                     return true;
1370                 case IDEActions.ProjectFolderRemoveItem:
1371                     removeProjectItem(a.objectParam);
1372                     return true;
1373                 case IDEActions.ProjectFolderRefresh:
1374                     refreshProjectItem(a.objectParam);
1375                     return true;
1376                 case IDEActions.ProjectFolderExpandAll:
1377                     _wsPanel.expandAll(a);
1378                     return true;
1379                 case IDEActions.ProjectFolderCollapseAll:
1380                     _wsPanel.collapseAll(a);
1381                     return true;
1382                 case IDEActions.CloseWorkspace:
1383                     closeWorkspace();
1384                     return true;
1385                 default:
1386                     return super.handleAction(a);
1387             }
1388         }
1389         return false;
1390     }
1391 
1392     @property ProjectSourceFile currentEditorSourceFile() {
1393         TabItem tab = _tabs.selectedTab;
1394         if (tab) {
1395             return cast(ProjectSourceFile)tab.objectParam;
1396         }
1397         return null;
1398     }
1399 
1400     void closeWorkspace() {
1401         if (currentWorkspace) {
1402             saveListOfOpenedFiles();
1403             currentWorkspace.save();
1404         }
1405         askForUnsavedEdits(delegate() {
1406             setWorkspace(null);
1407             showHomeScreen();
1408         });
1409     }
1410 
1411     void onBreakpointListChanged(ProjectSourceFile sourcefile, Breakpoint[] breakpoints) {
1412         if (!currentWorkspace)
1413             return;
1414         if (sourcefile) {
1415             currentWorkspace.setSourceFileBreakpoints(sourcefile, breakpoints);
1416         }
1417         if (_debugHandler)
1418             _debugHandler.onBreakpointListUpdated(currentWorkspace.getBreakpoints());
1419     }
1420 
1421     void onBookmarkListChanged(ProjectSourceFile sourcefile, EditorBookmark[] bookmarks) {
1422         if (!currentWorkspace)
1423             return;
1424         if (sourcefile)
1425             currentWorkspace.setSourceFileBookmarks(sourcefile, bookmarks);
1426     }
1427 
1428     void refreshProjectItem(const Object obj) {
1429         if (currentWorkspace is null)
1430             return;
1431         Project project;
1432         ProjectFolder folder;
1433         if (cast(Workspace)obj) {
1434             Workspace ws = cast(Workspace)obj;
1435             ws.refresh();
1436             refreshWorkspace();
1437         } else if (cast(Project)obj) {
1438             project = cast(Project)obj;
1439         } else if (cast(ProjectFolder)obj) {
1440             folder = cast(ProjectFolder)obj;
1441             project = folder.project;
1442         } else if (cast(ProjectSourceFile)obj) {
1443             ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1444             folder = cast(ProjectFolder)srcfile.parent;
1445             project = srcfile.project;
1446         } else {
1447             ProjectSourceFile srcfile = currentEditorSourceFile;
1448             if (srcfile) {
1449                 folder = cast(ProjectFolder)srcfile.parent;
1450                 project = srcfile.project;
1451             }
1452         }
1453         if (project) {
1454             project.refresh();
1455             refreshWorkspace();
1456         }
1457     }
1458 
1459     void removeProjectItem(const Object obj) {
1460         if (currentWorkspace is null)
1461             return;
1462         ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1463         if (!srcfile)
1464             return;
1465         Project project = srcfile.project;
1466         if (!project)
1467             return;
1468         window.showMessageBox(UIString.fromId("HEADER_REMOVE_FILE"c), 
1469                 UIString.fromId("QUESTION_REMOVE_FILE"c) ~ " " ~ srcfile.name ~ "?", 
1470                 [ACTION_YES, ACTION_NO], 
1471                 1, delegate(const Action result) {
1472                     if (result == StandardAction.Yes) {
1473                         // save and close
1474                         import std.file : remove;
1475                         closeTab(srcfile.filename);
1476                         try {
1477                             remove(srcfile.filename);
1478                         } catch (Exception e) {
1479                             Log.e("Cannot remove file");
1480                         }
1481                         project.refresh();
1482                         refreshWorkspace();
1483                     }
1484                     // else ignore
1485                     return true;
1486                 });
1487 
1488     }
1489 
1490     /// add new file to project
1491     void addProjectItem(Object obj) {
1492         if (currentWorkspace is null)
1493             return;
1494         if (obj is null && _wsPanel !is null && !currentEditorSourceFile) {
1495             obj = _wsPanel.selectedProjectItem;
1496             if (!obj)
1497                 obj = currentWorkspace.startupProject;
1498         }
1499         Project project;
1500         ProjectFolder folder;
1501         if (cast(Project)obj) {
1502             project = cast(Project)obj;
1503             folder = project.firstSourceFolder;
1504         } else if (cast(ProjectFolder)obj) {
1505             folder = cast(ProjectFolder)obj;
1506             project = folder.project;
1507         } else if (cast(ProjectSourceFile)obj) {
1508             ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1509             folder = cast(ProjectFolder)srcfile.parent;
1510             project = srcfile.project;
1511         } else {
1512             ProjectSourceFile srcfile = currentEditorSourceFile;
1513             if (srcfile) {
1514                 folder = cast(ProjectFolder)srcfile.parent;
1515                 project = srcfile.project;
1516             }
1517         }
1518         if (project && folder && project.workspace is currentWorkspace) {
1519             NewFileDlg dlg = new NewFileDlg(this, project, folder);
1520             dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1521                 if (result.id == ACTION_FILE_NEW_SOURCE_FILE.id) {
1522                     FileCreationResult res = cast(FileCreationResult)result.objectParam;
1523                     if (res) {
1524                         //res.project.reload();
1525                         res.project.refresh();
1526                         refreshWorkspace();
1527                         if (isSupportedSourceTextFileFormat(res.filename)) {
1528                             openSourceFile(res.filename, null, true);
1529                         }
1530                     }
1531                 }
1532             };
1533             dlg.show();
1534         }
1535     }
1536 
1537     void createNewProject(bool newWorkspace) {
1538         if (currentWorkspace is null)
1539             newWorkspace = true;
1540         string location = _settings.getRecentPath("FILE_OPEN_WORKSPACE_PATH");
1541         if (newWorkspace && location)
1542             location = location.dirName;
1543         NewProjectDlg dlg = new NewProjectDlg(this, newWorkspace, currentWorkspace, location);
1544         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1545             if (result.id == ACTION_FILE_NEW_PROJECT.id || result.id == ACTION_FILE_NEW_WORKSPACE.id) {
1546                 //Log.d("settings after edit:\n", s.toJSON(true));
1547                 ProjectCreationResult res = cast(ProjectCreationResult)result.objectParam;
1548                 if (res) {
1549                     // open workspace/project
1550                     if (currentWorkspace is null || res.workspace !is currentWorkspace) {
1551                         // open new workspace
1552                         setWorkspace(res.workspace);
1553                         refreshWorkspace();
1554                         hideHomeScreen();
1555                     } else {
1556                         // project added to current workspace
1557                         loadProject(res.project);
1558                         refreshWorkspace();
1559                         hideHomeScreen();
1560                     }
1561                 }
1562             }
1563         };
1564         dlg.show();
1565     }
1566 
1567     void showPreferences() {
1568         //Log.d("settings before copy:\n", _settings.setting.toJSON(true));
1569         Setting s = _settings.copySettings();
1570         //Log.d("settings after copy:\n", s.toJSON(true));
1571         SettingsDialog dlg = new SettingsDialog(UIString.fromId("HEADER_SETTINGS"c), window, s, createSettingsPages());
1572         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1573             if (result.id == ACTION_APPLY.id) {
1574                 //Log.d("settings after edit:\n", s.toJSON(true));
1575                 _settings.applySettings(s);
1576                 applySettings(_settings);
1577                 _settings.save();
1578             }
1579         };
1580         dlg.show();
1581     }
1582 
1583     void setStartupProject(Project project) {
1584         if (!currentWorkspace)
1585             return;
1586         if (!project)
1587             return;
1588         currentWorkspace.startupProject = project;
1589         warmUpImportPaths(project);
1590         if (_wsPanel)
1591             _wsPanel.updateDefault();
1592     }
1593 
1594     void showProjectSettings(Project project) {
1595         if (!currentWorkspace)
1596             return;
1597         if (!project)
1598             project = currentWorkspace.startupProject;
1599         if (!project)
1600             return;
1601         Setting s = project.settings.copySettings();
1602         SettingsDialog dlg = new SettingsDialog(UIString.fromRaw(project.name ~ " - "d ~ UIString.fromId("HEADER_PROJECT_SETTINGS"c)), window, s, createProjectSettingsPages());
1603         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1604             if (result.id == ACTION_APPLY.id) {
1605                 //Log.d("settings after edit:\n", s.toJSON(true));
1606                 project.settings.applySettings(s);
1607                 project.settings.save();
1608             }
1609         };
1610         dlg.show();
1611     }
1612 
1613     // Applying settings to tabs/sources and it's opening
1614     void applySettings(IDESettings settings) {
1615         _toolbarHost.visibility = _settings.showToolbar ? Visibility.Visible : Visibility.Gone;
1616         _statusLine.visibility = _settings.showStatusbar ? Visibility.Visible : Visibility.Gone;
1617         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
1618             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
1619             if (ed) {
1620                 applySettings(ed, settings);
1621             }
1622         }
1623         FontManager.fontGamma = settings.fontGamma;
1624         FontManager.hintingMode = settings.hintingMode;
1625         FontManager.minAnitialiasedFontSize = settings.minAntialiasedFontSize;
1626         Platform.instance.uiLanguage = settings.uiLanguage;
1627         Platform.instance.uiTheme = settings.uiTheme;
1628         bool needUpdateTheme = false;
1629         string oldFontFace = currentTheme.fontFace;
1630         string newFontFace = settings.uiFontFace;
1631         if (newFontFace == "Default")
1632             newFontFace = "Helvetica Neue,Verdana,Arial,DejaVu Sans,Liberation Sans,Helvetica,Roboto,Droid Sans";
1633         int oldFontSize = currentTheme.fontSize;
1634         if (oldFontFace != newFontFace) {
1635             currentTheme.fontFace = newFontFace;
1636             needUpdateTheme = true;
1637         }
1638         if (overrideScreenDPI != settings.screenDpiOverride) {
1639             overrideScreenDPI = settings.screenDpiOverride;
1640             needUpdateTheme = true;
1641         }
1642         if (oldFontSize != settings.uiFontSize) {
1643             currentTheme.fontSize = settings.uiFontSize;
1644             needUpdateTheme = true;
1645         }
1646         if (needUpdateTheme) {
1647             Log.d("updating theme after UI font change");
1648             Platform.instance.onThemeChanged();
1649         }
1650         requestLayout();
1651     }
1652 
1653     void applySettings(DSourceEdit editor, IDESettings settings) {
1654         editor.settings(settings).applySettings();
1655     }
1656 
1657     private bool loadProject(Project project) {
1658         if (!project.load()) {
1659             _logPanel.logLine("Cannot read project " ~ project.filename);
1660             window.showMessageBox(UIString.fromId("ERROR_OPEN_PROJECT"c).value, UIString.fromId("ERROR_OPENING_PROJECT"c).value ~ toUTF32(project.filename));
1661             return false;
1662         }
1663         const auto msg = UIString.fromId("MSG_OPENED_PROJECT"c);
1664         _logPanel.logLine(toUTF32("Project file " ~ project.filename ~  " is opened ok"));
1665         
1666         warmUpImportPaths(project);
1667         return true;
1668     }
1669 
1670     public void warmUpImportPaths(Project project) {
1671         dcdInterface.warmUp(project.importPaths(_settings));
1672     }
1673 
1674     void restoreListOfOpenedFiles() {
1675         // All was opened, attempt to restore files
1676         WorkspaceFile[] files = currentWorkspace.files();
1677         for (int i; i < files.length; i++) 
1678             with (files[i])
1679             {
1680                 // Opening file
1681                 if (openSourceFile(filename))
1682                 {
1683                     auto index = _tabs.tabIndex(filename);
1684                     if (index < 0)
1685                         continue;
1686                     // file is opened in tab
1687                     auto source = cast(DSourceEdit)_tabs.tabBody(filename);
1688                     if (!source)
1689                         continue;
1690                     // Caret position
1691                     source.setCaretPos(column, row, true, true);
1692                 }
1693             }
1694     }
1695 
1696     void saveListOfOpenedFiles() {
1697         WorkspaceFile[] files;
1698         for (auto i = 0; i < _tabs.tabCount(); i++)
1699         {
1700             auto edit = cast(DSourceEdit)_tabs.tabBody(i);
1701             if (edit !is null) {
1702                 auto file = new WorkspaceFile();
1703                 file.filename = edit.filename();
1704                 file.row = edit.caretPos.pos;
1705                 file.column = edit.caretPos.line;
1706                 files ~= file;
1707             }
1708         }
1709         currentWorkspace.files(files);
1710         // saving workspace
1711         currentWorkspace.save();
1712     }
1713 
1714     void openFileOrWorkspace(string filename) {
1715         // Open DlangIDE workspace file
1716         if (filename.isWorkspaceFile) {
1717             Workspace ws = new Workspace(this);
1718             if (ws.load(filename)) {
1719                 askForUnsavedEdits(delegate() {
1720                     setWorkspace(ws);
1721                     hideHomeScreen();
1722                     // Write workspace to recent workspaces list
1723                     _settings.updateRecentWorkspace(filename);
1724                     restoreListOfOpenedFiles();
1725                 });
1726             } else {
1727                 window.showMessageBox(UIString.fromId("ERROR_OPEN_WORKSPACE"c).value, UIString.fromId("ERROR_OPENING_WORKSPACE"c).value);
1728                 return;
1729             }
1730         } else if (filename.isProjectFile) { // Open non-DlangIDE project file or DlangIDE project
1731             _logPanel.clear();
1732             const auto msg = UIString.fromId("MSG_TRY_OPEN_PROJECT"c).value;
1733             _logPanel.logLine(msg ~ toUTF32(" " ~ filename));
1734             Project project = new Project(currentWorkspace, filename);
1735             if (!loadProject(project)) {
1736                 //window.showMessageBox(UIString.fromId("MSG_OPEN_PROJECT"c), UIString.fromId("ERROR_INVALID_WS_OR_PROJECT_FILE"c));
1737                 //_logPanel.logLine("File is not recognized as DlangIDE project or workspace file");
1738                 return;
1739             }
1740             string defWsFile = project.defWorkspaceFile;
1741             if (currentWorkspace) {
1742                 Project existing = currentWorkspace.findProject(project.filename);
1743                 if (existing) {
1744                     _logPanel.logLine("Project is already in workspace"d);
1745                     window.showMessageBox(UIString.fromId("MSG_OPEN_PROJECT"c), UIString.fromId("MSG_PROJECT_ALREADY_OPENED"c));
1746                     return;
1747                 }
1748                 window.showMessageBox(UIString.fromId("MSG_OPEN_PROJECT"c), UIString.fromId("QUESTION_NEW_WORKSPACE"c),
1749 
1750                                       [ACTION_ADD_TO_CURRENT_WORKSPACE, ACTION_CREATE_NEW_WORKSPACE, ACTION_CANCEL], 0, delegate(const Action result) {
1751                                           if (result.id == IDEActions.CreateNewWorkspace) {
1752                                               // new ws
1753                                               createNewWorkspaceForExistingProject(project);
1754                                               hideHomeScreen();
1755                                           } else if (result.id == IDEActions.AddToCurrentWorkspace) {
1756                                               // add to current
1757                                               currentWorkspace.addProject(project);
1758                                               loadProject(project);
1759                                               currentWorkspace.save();
1760                                               refreshWorkspace();
1761                                               hideHomeScreen();
1762                                           }
1763                                           return true;
1764                                       });
1765             } else {
1766                 // new workspace file
1767                 createNewWorkspaceForExistingProject(project);
1768             }
1769         } else {
1770             _logPanel.logLine("File is not recognized as DlangIDE project or workspace file");
1771             window.showMessageBox(UIString.fromId("ERROR_INVALID_WORKSPACE_FILE"c), UIString.fromId("ERROR_INVALID_WS_OR_PROJECT_FILE"c));
1772         }
1773     }
1774 
1775     void refreshWorkspace() {
1776         _logPanel.logLine("Refreshing workspace");
1777         _wsPanel.reloadItems();
1778         closeRemovedDocuments();
1779     }
1780 
1781     void createNewWorkspaceForExistingProject(Project project) {
1782         string defWsFile = project.defWorkspaceFile;
1783         _logPanel.logLine("Creating new workspace " ~ defWsFile);
1784         // new ws
1785         Workspace ws = new Workspace(this);
1786         ws.name = project.name;
1787         ws.description = project.description;
1788         Log.d("workspace name: ", project.name);
1789         Log.d("workspace description: ", project.description);
1790         ws.addProject(project);
1791         // Load project data
1792         loadProject(project);
1793         ws.save(defWsFile);
1794         setWorkspace(ws);
1795         _logPanel.logLine("Done");
1796     }
1797 
1798     //bool loadWorkspace(string path) {
1799     //    // testing workspace loader
1800     //    Workspace ws = new Workspace();
1801     //    ws.load(path);
1802     //    setWorkspace(ws);
1803     //    //ws.save(ws.filename ~ ".bak");
1804     //    return true;
1805     //}
1806 
1807     void setWorkspace(Workspace ws) {
1808         closeAllDocuments();
1809         currentWorkspace = ws;
1810         _wsPanel.workspace = ws;
1811         requestActionsUpdate();
1812         // Open main file for project
1813         if (ws && ws.startupProject && ws.startupProject.mainSourceFile 
1814             && (currentWorkspace.files == null || currentWorkspace.files.length == 0)) {
1815             openSourceFile(ws.startupProject.mainSourceFile.filename);
1816             _tabs.setFocus();
1817         }
1818         if (ws) {
1819             _wsPanel.activate();
1820             _settings.updateRecentWorkspace(ws.filename);
1821             _settings.setRecentPath(ws.dir, "FILE_OPEN_WORKSPACE_PATH");
1822             if (ws.startupProject) {
1823                 warmUpImportPaths(ws.startupProject);
1824             }
1825             window.windowCaption(ws.name ~ " - "d ~ frameWindowCaptionSuffix);
1826             _cbBuildConfiguration.enabled = true;
1827             _cbBuildConfiguration.selectedItemIndex = currentWorkspace.buildConfiguration;
1828             updateProjectConfigurations();
1829         } else {
1830             _cbBuildConfiguration.enabled = false;
1831             window.windowCaption(frameWindowCaptionSuffix);
1832             _wsPanel.hide();
1833             updateProjectConfigurations();
1834         }
1835 
1836     }
1837 
1838     void refreshProject(Project project) {
1839         if (currentWorkspace && project.loadSelections()) {
1840             currentWorkspace.cleanupUnusedDependencies();
1841             refreshWorkspace();
1842         }
1843     }
1844 
1845     void revealProjectInExplorer(Project project) {
1846         Platform.instance.showInFileManager(project.items.filename);
1847     }
1848 
1849     static bool canWrite(string filename) {
1850         import std.stdio : File;
1851         try {
1852             File f = File(filename, "a");
1853             scope(exit) f.close();
1854             return true;
1855         } catch (Exception e) {
1856             return false;
1857         }
1858     }
1859 
1860     void buildProject(BuildOperation buildOp, Project project, BuildResultListener listener = null) {
1861         if (!currentWorkspace) {
1862             _logPanel.logLine("No workspace is opened");
1863             return;
1864         }
1865         if (!project)
1866             project = currentWorkspace.startupProject;
1867         if (!project) {
1868             _logPanel.logLine("No project is opened");
1869             return;
1870         }
1871         _logPanel.activateLogTab();
1872         string baseDirectory = project.dir;
1873         Log.d("build: base directory is ", baseDirectory);
1874         _logPanel.setLogWidgetBaseDirectory(baseDirectory);
1875         if (!listener) {
1876             if (buildOp == BuildOperation.Upgrade || buildOp == BuildOperation.Build || buildOp == BuildOperation.Rebuild) {
1877                 listener = delegate(int result) {
1878                     if (!result) {
1879                         // success: update workspace
1880                         refreshProject(project);
1881                     } else {
1882                         handleBuildError(result, project);
1883                     }
1884                 };
1885             }
1886         }
1887         ProjectSettings projectSettings = project.settings;
1888         string toolchain = projectSettings.getToolchain(_settings);
1889         string arch = projectSettings.getArch(_settings);
1890         string dubExecutable = _settings.dubExecutable;
1891         string dubAdditionalParams = projectSettings.getDubAdditionalParams(_settings);
1892 
1893         string exeFile = project.executableFileName;
1894         if (exeFile && (buildOp == BuildOperation.Build || buildOp == BuildOperation.Rebuild || buildOp == BuildOperation.Clean || buildOp == BuildOperation.Run)) {
1895             import std.file : isFile, exists;
1896             if (exeFile.exists && exeFile.isFile) {
1897                 if (!canWrite(exeFile)) {
1898                     _logPanel.clear();
1899                     _logPanel.logLine("Executable file is in use. Stop runing application before build.");
1900                     handleBuildError(-5, project);
1901                     return;
1902                 }
1903             }
1904         }
1905 
1906         Builder op = new Builder(this, project, _logPanel, project.projectConfiguration, currentWorkspace.buildConfiguration, buildOp, 
1907                                  dubExecutable, dubAdditionalParams,
1908                                  toolchain,
1909                                  arch,
1910                                  listener);
1911         setBackgroundOperation(op);
1912     }
1913     
1914     void updateProjectConfigurations() {
1915         if (currentWorkspace && currentWorkspace.startupProject) {
1916             if (_projectConfigurationCombo) {
1917                 _projectConfigurationCombo.enabled = true;
1918                 _projectConfigurationCombo.itemClick.clear();
1919                 dstring[] items = currentWorkspace.startupProject.configurationNames;
1920                 _projectConfigurationCombo.items = items;
1921                 _projectConfigurationCombo.selectedItemIndex = currentWorkspace.startupProject.projectConfigurationIndex;
1922                 _projectConfigurationCombo.itemClick = delegate(Widget source, int index) {
1923                     if (currentWorkspace) {
1924                         currentWorkspace.setStartupProjectConfiguration(_projectConfigurationCombo.selectedItem.to!string); 
1925                     }
1926                     return true;
1927                 };
1928             }
1929             if (_projectConfigurationMenuItem) {
1930                 _projectConfigurationMenuItem.clear();
1931                 foreach (config; currentWorkspace.startupProject.configurations) {
1932                     Action a = ACTION_PROJECT_CONFIGURATION.clone;
1933                     a.label = config.name.toUTF32;
1934                     a.stringParam = config.name;
1935                     MenuItem child = new MenuItem(a);
1936                     child.type = MenuItemType.Radio;
1937                     _projectConfigurationMenuItem.add(child);
1938                 }
1939             }
1940         } else {
1941             if (_projectConfigurationCombo) {
1942                 _projectConfigurationCombo.itemClick.clear();
1943                 _projectConfigurationCombo.enabled = false;
1944                 _projectConfigurationCombo.items = ["default"d];
1945             }
1946             if (_projectConfigurationMenuItem) {
1947                 // TODO
1948             }
1949         }
1950     }
1951 
1952     /// handle files dropped to application window
1953     void onFilesDropped(string[] filenames) {
1954         //Log.d("onFilesDropped(", filenames, ")");
1955         bool first = true;
1956         for (int i = 0; i < filenames.length; i++) {
1957             openSourceFile(filenames[i], null, first);
1958             first = false;
1959         }
1960     }
1961 
1962     void restoreUIStateOnStartup() {
1963         window.restoreWindowState(_settings.uiState);
1964     }
1965 
1966     /// return false to prevent closing
1967     bool onCanClose() {
1968         askForUnsavedEdits(delegate() {
1969             if (currentWorkspace) {
1970                 // Remember opened files
1971                 saveListOfOpenedFiles();
1972             }
1973             window.close();
1974         });
1975         return false;
1976     }
1977     /// called when main window is closing
1978     void onWindowClose() {
1979         window.saveWindowState(_settings.uiState);
1980         _settings.save();
1981         Log.i("onWindowClose()");
1982         stopExecution();
1983     }
1984 }
1985