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 
40 import std.conv;
41 import std.utf;
42 import std.algorithm : equal, endsWith;
43 import std.array : empty;
44 import std.string : split;
45 import std.path;
46 
47 immutable string HELP_PAGE_URL = "https://github.com/buggins/dlangide/wiki";
48 // TODO: get version from GIT commit
49 immutable dstring DLANGIDE_VERSION = "v0.7.42"d;
50 
51 bool isSupportedSourceTextFileFormat(string filename) {
52     return (filename.endsWith(".d") || filename.endsWith(".di") || filename.endsWith(".dt") || filename.endsWith(".txt") || filename.endsWith(".cpp") || filename.endsWith(".h") || filename.endsWith(".c")
53         || filename.endsWith(".json") || filename.endsWith(".sdl") || filename.endsWith(".dd") || filename.endsWith(".ddoc") || filename.endsWith(".xml") || filename.endsWith(".html")
54         || filename.endsWith(".html") || filename.endsWith(".css") || filename.endsWith(".log") || filename.endsWith(".hpp"));
55 }
56 
57 class BackgroundOperationWatcherTest : BackgroundOperationWatcher {
58     this(AppFrame frame) {
59         super(frame);
60     }
61     int _counter;
62     /// returns description of background operation to show in status line
63     override @property dstring description() { return "Test progress: "d ~ to!dstring(_counter); }
64     /// returns icon of background operation to show in status line
65     override @property string icon() { return "folder"; }
66     /// update background operation status
67     override void update() {
68         _counter++;
69         if (_counter >= 100)
70             _finished = true;
71         super.update();
72     }
73 }
74 
75 /// DIDE app frame
76 class IDEFrame : AppFrame, ProgramExecutionStatusListener, BreakpointListChangeListener, BookmarkListChangeListener {
77 
78     private ToolBarComboBox projectConfigurationCombo;
79     
80     MenuItem mainMenuItems;
81     WorkspacePanel _wsPanel;
82     OutputPanel _logPanel;
83     DockHost _dockHost;
84     TabWidget _tabs;
85     // Is any workspace already opened?
86     private auto openedWorkspace = false;
87 
88     ///Cache for parsed D files for autocomplete and symbol finding
89     import dlangide.tools.d.dcdinterface;
90     private DCDInterface _dcdInterface;
91     @property DCDInterface dcdInterface() {
92         if (!_dcdInterface)
93             _dcdInterface = new DCDInterface();
94         return _dcdInterface; 
95     }
96 
97     IDESettings _settings;
98     ProgramExecution _execution;
99 
100     dstring frameWindowCaptionSuffix = "DLangIDE"d;
101 
102     this(Window window) {
103         super();
104         window.mainWidget = this;
105         window.onFilesDropped = &onFilesDropped;
106         window.onCanClose = &onCanClose;
107         window.onClose = &onWindowClose;
108         applySettings(_settings);
109     }
110 
111     ~this() {
112         if (_dcdInterface) {
113             destroy(_dcdInterface);
114             _dcdInterface = null;
115         }
116     }
117 
118     @property DockHost dockHost() { return _dockHost; }
119     @property OutputPanel logPanel() { return _logPanel; }
120 
121     /// stop current program execution
122     void stopExecution() {
123         if (_execution) {
124             _logPanel.logLine("Stopping program execution");
125             Log.d("Stopping execution");
126             _execution.stop();
127             //destroy(_execution);
128             _execution = null;
129         }
130     }
131 
132     /// returns true if program execution or debugging is active
133     @property bool isExecutionActive() {
134         return _execution !is null;
135     }
136     
137     /// Is any workspace already opened?
138     @property bool isOpenedWorkspace() {
139         return openedWorkspace;
140     }
141     
142     /// Is any workspace already opened?
143     @property void isOpenedWorkspace(bool opened) {
144         openedWorkspace = opened;    
145     }
146 
147     /// called when program execution is stopped
148     protected void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) {
149         executeInUiThread(delegate() {
150             Log.d("onProgramExecutionStatus process: ", process.executableFile, " status: ", status, " exitCode: ", exitCode);
151             _execution = null;
152             // TODO: update state
153             switch(status) {
154                 case ExecutionStatus.Error:
155                     _logPanel.logLine("Cannot run program " ~ process.executableFile);
156                     break;
157                 case ExecutionStatus.Finished:
158                     _logPanel.logLine("Program " ~ process.executableFile ~ " finished with exit code " ~ to!string(exitCode));
159                     break;
160                 case ExecutionStatus.Killed:
161                     _logPanel.logLine("Program " ~ process.executableFile ~ " is killed");
162                     break;
163                 default:
164                     _logPanel.logLine("Program " ~ process.executableFile ~ " is finished");
165                     break;
166             }
167             _statusLine.setBackgroundOperationStatus(null, null);
168         });
169     }
170 
171     protected void handleBuildError(int result, Project project) {
172         ErrorPosition err = _logPanel.firstError;
173         if (err) {
174             onCompilerLogIssueClick(err.filename, err.line, err.pos);
175         }
176     }
177 
178     protected void buildAndDebugProject(Project project) {
179         if (!currentWorkspace)
180             return;
181         if (!project)
182             project = currentWorkspace.startupProject;
183         if (!project) {
184             window.showMessageBox(UIString.fromRaw("Cannot debug project"d), UIString.fromRaw("Startup project is not specified"d));
185             return;
186         }
187         buildProject(BuildOperation.Build, project, delegate(int result) {
188             if (!result) {
189                 Log.i("Build completed successfully. Starting debug for project.");
190                 debugProject(project);
191             } else {
192                 handleBuildError(result, project);
193             }
194         });
195     }
196 
197     void debugFinished(ProgramExecution process, ExecutionStatus status, int exitCode) {
198         _execution = null;
199         _debugHandler = null;
200         switch(status) {
201             case ExecutionStatus.Error:
202                 _logPanel.logLine("Cannot run program " ~ process.executableFile);
203                 _logPanel.activateLogTab();
204                 break;
205             case ExecutionStatus.Finished:
206                 _logPanel.logLine("Program " ~ process.executableFile ~ " finished with exit code " ~ to!string(exitCode));
207                 break;
208             case ExecutionStatus.Killed:
209                 _logPanel.logLine("Program " ~ process.executableFile ~ " is killed");
210                 break;
211             default:
212                 _logPanel.logLine("Program " ~ process.executableFile ~ " is finished");
213                 break;
214         }
215         _statusLine.setBackgroundOperationStatus(null, null);
216     }
217 
218     DebuggerUIHandler _debugHandler;
219     protected void debugProject(Project project) {
220         import std.file;
221         stopExecution();
222         if (!project) {
223             window.showMessageBox(UIString.fromRaw("Cannot debug project"d), UIString.fromRaw("Startup project is not specified"d));
224             return;
225         }
226         string executableFileName = project.executableFileName;
227         if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) {
228             window.showMessageBox(UIString.fromRaw("Cannot debug project"d), UIString.fromRaw("Cannot find executable file"d));
229             return;
230         }
231         string debuggerExecutable = _settings.debuggerExecutable;
232         if (debuggerExecutable.empty) {
233             window.showMessageBox(UIString.fromRaw("Cannot debug project"d), UIString.fromRaw("No debugger executable specified in settings"d));
234             return;
235         }
236 
237         GDBInterface program = new GDBInterface();
238         DebuggerProxy debuggerProxy = new DebuggerProxy(program, &executeInUiThread);
239         debuggerProxy.setDebuggerExecutable(debuggerExecutable);
240         setExecutableParameters(debuggerProxy, project, executableFileName);
241         _execution = debuggerProxy;
242         _debugHandler = new DebuggerUIHandler(this, debuggerProxy);
243         _debugHandler.onBreakpointListUpdated(currentWorkspace.getBreakpoints());
244         _debugHandler.run();
245     }
246 
247     protected void buildAndRunProject(Project project) {
248         if (!currentWorkspace)
249             return;
250         if (!project)
251             project = currentWorkspace.startupProject;
252         if (!project) {
253             window.showMessageBox(UIString.fromRaw("Cannot run project"d), UIString.fromRaw("Startup project is not specified"d));
254             return;
255         }
256         buildProject(BuildOperation.Build, project, delegate(int result) {
257             if (!result) {
258                 Log.i("Build completed successfully. Running program...");
259                 runProject(project);
260             } else {
261                 handleBuildError(result, project);
262             }
263         });
264     }
265 
266     protected void runProject(Project project) {
267         import std.file;
268         stopExecution();
269         if (!project) {
270             window.showMessageBox(UIString.fromRaw("Cannot run project"d), UIString.fromRaw("Startup project is not specified"d));
271             return;
272         }
273         string executableFileName = project.executableFileName;
274         if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) {
275             window.showMessageBox(UIString.fromRaw("Cannot run project"d), UIString.fromRaw("Cannot find executable file"d));
276             return;
277         }
278         auto program = new ProgramExecutionNoDebug;
279         setExecutableParameters(program, project, executableFileName);
280         program.setProgramExecutionStatusListener(this);
281         _execution = program;
282         program.run();
283     }
284 
285     bool setExecutableParameters(ProgramExecution program, Project project, string executableFileName) {
286         string[] args;
287         string externalConsoleExecutable = null;
288         string workingDirectory = project.workingDirectory;
289         string tty = _logPanel.terminalDeviceName;
290         if (project.runInExternalConsole) {
291             version(Windows) {
292                 if (program.isMagoDebugger)
293                     tty = "external-console";
294             } else {
295                 externalConsoleExecutable = _settings.terminalExecutable;
296             }
297         }
298         if (!program.isDebugger)
299             _logPanel.logLine("MSG_STARTING"c ~ " " ~ executableFileName);
300         else
301             _logPanel.logLine("MSG_STARTING_DEBUGGER"c ~ " " ~ executableFileName);
302         const auto status =  program.isDebugger ?  UIString.fromId("DEBUGGING"c).value : UIString.fromId("RUNNING"c).value;
303         _statusLine.setBackgroundOperationStatus("debug-run", status);
304         string[string] env;
305         program.setExecutableParams(executableFileName, args, workingDirectory, env);
306         if (!tty.empty) {
307             Log.d("Terminal window device name: ", tty);
308             program.setTerminalTty(tty);
309             if (tty != "external-console")
310                 _logPanel.activateTerminalTab(true);
311         } else
312             program.setTerminalExecutable(externalConsoleExecutable);
313         return true;
314     }
315 
316     void runWithRdmd(string filename) {
317         stopExecution();
318 
319         string rdmdExecutable = _settings.rdmdExecutable;
320 
321         auto program = new ProgramExecutionNoDebug;
322         string sourceFileName = baseName(filename);
323         string workingDirectory = dirName(filename);
324         string[] args;
325         {
326             string rdmdAdditionalParams = _settings.rdmdAdditionalParams;
327             if (!rdmdAdditionalParams.empty)
328                 args ~= rdmdAdditionalParams.split();
329 
330             auto buildConfig = currentWorkspace ? currentWorkspace.buildConfiguration : BuildConfiguration.Debug;
331             switch (buildConfig) {
332                 default:
333                 case BuildConfiguration.Debug:
334                     args ~= "-debug";
335                     break;
336                 case BuildConfiguration.Release:
337                     args ~= "-release";
338                     break;
339                 case BuildConfiguration.Unittest:
340                     args ~= "-unittest";
341                     break;
342             }
343             args ~= sourceFileName;
344         }
345         string externalConsoleExecutable = null;
346         version(Windows) {
347         } else {
348             externalConsoleExecutable = _settings.terminalExecutable;
349         }
350         _logPanel.logLine("Starting " ~ sourceFileName ~ " with rdmd");
351         _statusLine.setBackgroundOperationStatus("run-rdmd", "running..."d);
352         program.setExecutableParams(rdmdExecutable, args, workingDirectory, null);
353         program.setTerminalExecutable(externalConsoleExecutable);
354         program.setProgramExecutionStatusListener(this);
355         _execution = program;
356         program.run();
357     }
358 
359     override protected void initialize() {
360         _appName = "dlangide";
361         //_editorTool = new DEditorTool(this);
362         _settings = new IDESettings(buildNormalizedPath(settingsDir, "settings.json"));
363         _settings.load();
364         _settings.updateDefaults();
365         _settings.save();
366         super.initialize();
367     }
368 
369     /// move focus to editor in currently selected tab
370     void focusEditor(string id) {
371         Widget w = _tabs.tabBody(id);
372         if (w) {
373             if (w.visible)
374                 w.setFocus();
375         }
376     }
377 
378     /// source file selected in workspace tree
379     bool onSourceFileSelected(ProjectSourceFile file, bool activate) {
380         Log.d("onSourceFileSelected ", file.filename, " activate=", activate);
381         if (activate)
382             return openSourceFile(file.filename, file, activate);
383         return false;
384     }
385 
386     /// returns global IDE settings
387     @property IDESettings settings() { return _settings; }
388 
389     ///
390     bool onCompilerLogIssueClick(dstring filename, int line, int column)
391     {
392         Log.d("onCompilerLogIssueClick ", filename);
393 
394         import std.conv:to;
395         openSourceFile(to!string(filename));
396 
397         currentEditor().setCaretPos(line, 0);
398         currentEditor().setCaretPos(line, column);
399 
400         return true;
401     }
402 
403     void onModifiedStateChange(Widget source, bool modified) {
404         //
405         Log.d("onModifiedStateChange ", source.id, " modified=", modified);
406         int index = _tabs.tabIndex(source.id);
407         if (index >= 0) {
408             dstring name = toUTF32((modified ? "* " : "") ~ baseName(source.id));
409             _tabs.renameTab(index, name);
410         }
411     }
412 
413     bool openSourceFile(string filename, ProjectSourceFile file = null, bool activate = true) {
414         if (!file && !filename)
415             return false;
416         if (!file)
417             file = _wsPanel.findSourceFileItem(filename, false);
418 
419         //if(!file)
420         //    return false;
421 
422         if (file)
423             filename = file.filename;
424 
425         Log.d("openSourceFile ", filename);
426         int index = _tabs.tabIndex(filename);
427         if (index >= 0) {
428             // file is already opened in tab
429             _tabs.selectTab(index, true);
430         } else {
431             // open new file
432             DSourceEdit editor = new DSourceEdit(filename);
433             if (file ? editor.load(file) : editor.load(filename)) {
434                 _tabs.addTab(editor, toUTF32(baseName(filename)), null, true);
435                 index = _tabs.tabIndex(filename);
436                 TabItem tab = _tabs.tab(filename);
437                 tab.objectParam = file;
438                 editor.modifiedStateChange = &onModifiedStateChange;
439                 if (file) {
440                     editor.breakpointListChanged = this; //onBreakpointListChanged
441                     editor.bookmarkListChanged = this; //onBreakpointListChanged
442                     editor.setBreakpointList(currentWorkspace.getSourceFileBreakpoints(file));
443                     editor.setBookmarkList(currentWorkspace.getSourceFileBookmarks(file));
444                 }
445                 applySettings(editor, settings);
446                 _tabs.selectTab(index, true);
447                 if( filename.endsWith(".d") || filename.endsWith(".di") )
448                     editor.editorTool = new DEditorTool(this);
449                 else
450                     editor.editorTool = new DefaultEditorTool(this);
451             } else {
452                 destroy(editor);
453                 if (window)
454                     window.showMessageBox(UIString.fromRaw("File open error"d), UIString.fromRaw("Failed to open file "d ~ toUTF32(file.filename)));
455                 return false;
456             }
457         }
458         if (activate) {
459             focusEditor(filename);
460         }
461         requestLayout();
462         return true;
463     }
464 
465     static immutable HOME_SCREEN_ID = "HOME_SCREEN";
466     void showHomeScreen() {
467         int index = _tabs.tabIndex(HOME_SCREEN_ID);
468         if (index >= 0) {
469             _tabs.selectTab(index, true);
470         } else {
471             HomeScreen home = new HomeScreen(HOME_SCREEN_ID, this);
472             _tabs.addTab(home, UIString.fromId("HOME"c), null, true);
473             _tabs.selectTab(HOME_SCREEN_ID, true);
474              auto _settings = new IDESettings(buildNormalizedPath(settingsDir, "settings.json"));
475             // Auto open last workspace, if no workspace specified in command line and autoOpen flag set to true
476             const auto recentWorkspaces = settings.recentWorkspaces;
477             if (!openedWorkspace && recentWorkspaces.length > 0 && _settings.autoOpenLastProject())
478             {
479                 Action a = ACTION_FILE_OPEN_WORKSPACE.clone();
480                 a.stringParam = recentWorkspaces[0];
481                 handleAction(a);
482             }
483         }
484     }
485 
486     void hideHomeScreen() {
487         _tabs.removeTab(HOME_SCREEN_ID);
488     }
489 
490     void onTabChanged(string newActiveTabId, string previousTabId) {
491         int index = _tabs.tabIndex(newActiveTabId);
492         if (index >= 0) {
493             TabItem tab = _tabs.tab(index);
494             ProjectSourceFile file = cast(ProjectSourceFile)tab.objectParam;
495             if (file) {
496                 //setCurrentProject(file.project);
497                 // tab is source file editor
498                 _wsPanel.selectItem(file);
499                 focusEditor(file.filename);
500             }
501             window.windowCaption(tab.text.value ~ " - "d ~ frameWindowCaptionSuffix);
502         }
503         requestActionsUpdate();
504     }
505 
506     // returns DSourceEdit from currently active tab (if it's editor), null if current tab is not editor or no tabs open
507     DSourceEdit currentEditor() {
508         return cast(DSourceEdit)_tabs.selectedTabBody();
509     }
510 
511     /// close tab w/o confirmation
512     void closeTab(string tabId) {
513         _wsPanel.selectItem(null);
514         _tabs.removeTab(tabId);
515     }
516 
517     /// close all editor tabs
518     void closeAllDocuments() {
519         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
520             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
521             if (ed) {
522                 closeTab(ed.id);
523             }
524         }
525     }
526 
527     /// returns array of all opened source editors
528     DSourceEdit[] allOpenedEditors() {
529         DSourceEdit[] res;
530         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
531             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
532             if (ed) {
533                 res ~= ed;
534             }
535         }
536         return res;
537     }
538 
539     /// close editor tabs for which files are removed from filesystem
540     void closeRemovedDocuments() {
541         import std.file;
542         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
543             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
544             if (ed) {
545                 if (!exists(ed.id) || !isFile(ed.id)) {
546                     closeTab(ed.id);
547                 }
548             }
549         }
550     }
551 
552     /// returns first unsaved document
553     protected DSourceEdit hasUnsavedEdits() {
554         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
555             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
556             if (ed && ed.content.modified) {
557                 return ed;
558             }
559         }
560         return null;
561     }
562 
563     protected void askForUnsavedEdits(void delegate() onConfirm) {
564         DSourceEdit ed = hasUnsavedEdits();
565         if (!ed) {
566             // no unsaved edits
567             onConfirm();
568             return;
569         }
570         string tabId = ed.id;
571         // tab content is modified - ask for confirmation
572         window.showMessageBox(UIString.fromRaw("Close file "d ~ toUTF32(baseName(tabId))), UIString.fromRaw("Content of this file has been changed."d), 
573                               [ACTION_SAVE, ACTION_SAVE_ALL, ACTION_DISCARD_CHANGES, ACTION_DISCARD_ALL, ACTION_CANCEL], 
574                               0, delegate(const Action result) {
575                                   if (result == StandardAction.Save) {
576                                       // save and close
577                                       ed.save();
578                                       askForUnsavedEdits(onConfirm);
579                                   } else if (result == StandardAction.DiscardChanges) {
580                                       // close, don't save
581                                       closeTab(tabId);
582                                       closeAllDocuments();
583                                       onConfirm();
584                                   } else if (result == StandardAction.SaveAll) {
585                                       ed.save();
586                                       for(;;) {
587                                           DSourceEdit editor = hasUnsavedEdits();
588                                           if (!editor)
589                                               break;
590                                           editor.save();
591                                       }
592                                       closeAllDocuments();
593                                       onConfirm();
594                                   } else if (result == StandardAction.DiscardAll) {
595                                       // close, don't save
596                                       closeAllDocuments();
597                                       onConfirm();
598                                   }
599                                   // else ignore
600                                   return true;
601                               });
602     }
603 
604     protected void onTabClose(string tabId) {
605         Log.d("onTabClose ", tabId);
606         int index = _tabs.tabIndex(tabId);
607         if (index >= 0) {
608             DSourceEdit d = cast(DSourceEdit)_tabs.tabBody(tabId);
609             if (d && d.content.modified) {
610                 // tab content is modified - ask for confirmation
611                 window.showMessageBox(UIString.fromRaw("Close tab"d), UIString.fromRaw("Content of "d ~ toUTF32(baseName(tabId)) ~ " file has been changed."d), 
612                                       [ACTION_SAVE, ACTION_DISCARD_CHANGES, ACTION_CANCEL], 
613                                       0, delegate(const Action result) {
614                                           if (result == StandardAction.Save) {
615                                               // save and close
616                                               d.save();
617                                               closeTab(tabId);
618                                           } else if (result == StandardAction.DiscardChanges) {
619                                               // close, don't save
620                                               closeTab(tabId);
621                                           }
622                                           // else ignore
623                                           return true;
624                                       });
625             } else {
626                 closeTab(tabId);
627             }
628         }
629         requestActionsUpdate();
630     }
631 
632     /// create app body widget
633     override protected Widget createBody() {
634         _dockHost = new DockHost();
635 
636         //=============================================================
637         // Create body - Tabs
638 
639         // editor tabs
640         _tabs = new TabWidget("TABS");
641         _tabs.hiddenTabsVisibility = Visibility.Gone;
642         //_tabs.setStyles(STYLE_DOCK_HOST_BODY, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT);
643         _tabs.setStyles(STYLE_DOCK_WINDOW, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT, STYLE_DOCK_HOST_BODY);
644         _tabs.tabChanged = &onTabChanged;
645         _tabs.tabClose = &onTabClose;
646 
647         _dockHost.bodyWidget = _tabs;
648 
649         //=============================================================
650         // Create workspace docked panel
651         _wsPanel = new WorkspacePanel("workspace");
652         _wsPanel.sourceFileSelectionListener = &onSourceFileSelected;
653         _wsPanel.workspaceActionListener = &handleAction;
654         _wsPanel.dockAlignment = DockAlignment.Left;
655         _dockHost.addDockedWindow(_wsPanel);
656 
657         _logPanel = new OutputPanel("output");
658         _logPanel.compilerLogIssueClickHandler = &onCompilerLogIssueClick;
659         _logPanel.appendText(null, "DlangIDE is started\nHINT: Try to open some DUB project\n"d);
660         string dubPath = findExecutablePath("dub");
661         string rdmdPath = findExecutablePath("rdmd");
662         string dmdPath = findExecutablePath("dmd");
663         string ldcPath = findExecutablePath("ldc2");
664         string gdcPath = findExecutablePath("gdc");
665         _logPanel.appendText(null, dubPath ? ("dub path: "d ~ toUTF32(dubPath) ~ "\n"d) : ("dub is not found! cannot build projects without DUB\n"d));
666         _logPanel.appendText(null, rdmdPath ? ("rdmd path: "d ~ toUTF32(rdmdPath) ~ "\n"d) : ("rdmd is not found!\n"d));
667         _logPanel.appendText(null, dmdPath ? ("dmd path: "d ~ toUTF32(dmdPath) ~ "\n"d) : ("dmd compiler is not found!\n"d));
668         _logPanel.appendText(null, ldcPath ? ("ldc path: "d ~ toUTF32(ldcPath) ~ "\n"d) : ("ldc compiler is not found!\n"d));
669         _logPanel.appendText(null, gdcPath ? ("gdc path: "d ~ toUTF32(gdcPath) ~ "\n"d) : ("gdc compiler is not found!\n"d));
670 
671         _dockHost.addDockedWindow(_logPanel);
672 
673         return _dockHost;
674     }
675 
676     /// create main menu
677     override protected MainMenu createMainMenu() {
678 
679         mainMenuItems = new MenuItem();
680         MenuItem fileItem = new MenuItem(new Action(1, "MENU_FILE"));
681         MenuItem fileNewItem = new MenuItem(new Action(1, "MENU_FILE_NEW"));
682         fileNewItem.add(ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT);
683         fileItem.add(fileNewItem);
684         fileItem.add(ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 
685                      ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_WORKSPACE_CLOSE, ACTION_FILE_EXIT);
686 
687         MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT"));
688         editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, 
689                      ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_FIND_TEXT, ACTION_EDITOR_TOGGLE_BOOKMARK);
690         MenuItem editItemAdvanced = new MenuItem(new Action(221, "MENU_EDIT_ADVANCED"));
691         editItemAdvanced.add(ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS);
692         editItem.add(editItemAdvanced);
693 
694         editItem.add(ACTION_EDIT_PREFERENCES);
695 
696         MenuItem navItem = new MenuItem(new Action(21, "MENU_NAVIGATE"));
697         navItem.add(ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS, ACTION_GET_DOC_COMMENTS, ACTION_GET_PAREN_COMPLETION, ACTION_EDITOR_GOTO_PREVIOUS_BOOKMARK, ACTION_EDITOR_GOTO_NEXT_BOOKMARK);
698 
699         MenuItem projectItem = new MenuItem(new Action(21, "MENU_PROJECT"));
700         projectItem.add(ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, ACTION_PROJECT_SETTINGS);
701 
702         MenuItem buildItem = new MenuItem(new Action(22, "MENU_BUILD"));
703         buildItem.add(ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
704                      ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN,
705                      ACTION_RUN_WITH_RDMD);
706 
707         MenuItem debugItem = new MenuItem(new Action(23, "MENU_DEBUG"));
708         debugItem.add(ACTION_DEBUG_START, ACTION_DEBUG_START_NO_DEBUG, 
709                       ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE,
710                       ACTION_DEBUG_RESTART,
711                       ACTION_DEBUG_STEP_INTO,
712                       ACTION_DEBUG_STEP_OVER,
713                       ACTION_DEBUG_STEP_OUT,
714                       ACTION_DEBUG_TOGGLE_BREAKPOINT, ACTION_DEBUG_ENABLE_BREAKPOINT, ACTION_DEBUG_DISABLE_BREAKPOINT
715                       );
716 
717 
718         MenuItem windowItem = new MenuItem(new Action(3, "MENU_WINDOW"c));
719         //windowItem.add(new Action(30, "MENU_WINDOW_PREFERENCES"));
720         windowItem.add(ACTION_WINDOW_CLOSE_DOCUMENT);
721         windowItem.add(ACTION_WINDOW_CLOSE_ALL_DOCUMENTS);
722         MenuItem helpItem = new MenuItem(new Action(4, "MENU_HELP"c));
723         helpItem.add(ACTION_HELP_VIEW_HELP);
724         helpItem.add(ACTION_HELP_ABOUT);
725         mainMenuItems.add(fileItem);
726         mainMenuItems.add(editItem);
727         mainMenuItems.add(projectItem);
728         mainMenuItems.add(navItem);
729         mainMenuItems.add(buildItem);
730         mainMenuItems.add(debugItem);
731         //mainMenuItems.add(viewItem);
732         mainMenuItems.add(windowItem);
733         mainMenuItems.add(helpItem);
734 
735         MainMenu mainMenu = new MainMenu(mainMenuItems);
736         //mainMenu.backgroundColor = 0xd6dbe9;
737         return mainMenu;
738     }
739 
740     /// override it
741     override protected void updateShortcuts() {
742         if (applyShortcutsSettings()) {
743             Log.d("Shortcut actions loaded");
744         } else {
745             Log.d("Saving default shortcuts");
746             const(Action)[] actions;
747             actions ~= [
748                 ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, 
749                 ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, 
750                 ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, 
751                 ACTION_EDIT_PREFERENCES, 
752                 ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT, ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 
753                 ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_EXIT, 
754                 ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, 
755                 ACTION_PROJECT_SETTINGS, ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
756                 ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN, ACTION_RUN_WITH_RDMD,
757                 ACTION_DEBUG_START, ACTION_DEBUG_START_NO_DEBUG, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE,
758                 ACTION_DEBUG_RESTART,
759                 ACTION_DEBUG_STEP_INTO,
760                 ACTION_DEBUG_STEP_OVER,
761                 ACTION_DEBUG_STEP_OUT,
762                 ACTION_WINDOW_CLOSE_DOCUMENT, ACTION_WINDOW_CLOSE_ALL_DOCUMENTS, ACTION_HELP_ABOUT];
763             actions ~= STD_EDITOR_ACTIONS;
764             saveShortcutsSettings(actions);
765         }
766     }
767 
768     /// create app toolbars
769     override protected ToolBarHost createToolbars() {
770         ToolBarHost res = new ToolBarHost();
771         ToolBar tb;
772         tb = res.getOrAddToolbar("Standard");
773         tb.addButtons(ACTION_FILE_OPEN, ACTION_FILE_SAVE, ACTION_SEPARATOR);
774 
775         tb.addButtons(ACTION_DEBUG_START);
776         
777         projectConfigurationCombo = new ToolBarComboBox("projectConfig", [ProjectConfiguration.DEFAULT_NAME.to!dstring]);//Updateable
778         projectConfigurationCombo.itemClick = delegate(Widget source, int index) {
779             if (currentWorkspace) {
780                 currentWorkspace.setStartupProjectConfiguration(projectConfigurationCombo.selectedItem.to!string); 
781             }
782             return true;
783         };
784         projectConfigurationCombo.action = ACTION_PROJECT_CONFIGURATIONS;
785         tb.addControl(projectConfigurationCombo);
786         
787         ToolBarComboBox cbBuildConfiguration = new ToolBarComboBox("buildConfig", ["Debug"d, "Release"d, "Unittest"d]);
788         cbBuildConfiguration.itemClick = delegate(Widget source, int index) {
789             if (currentWorkspace && index < 3) {
790                 currentWorkspace.buildConfiguration = [BuildConfiguration.Debug, BuildConfiguration.Release, BuildConfiguration.Unittest][index];
791             }
792             return true;
793         };
794         cbBuildConfiguration.action = ACTION_BUILD_CONFIGURATIONS;
795         tb.addControl(cbBuildConfiguration);
796         tb.addButtons(ACTION_PROJECT_BUILD, ACTION_SEPARATOR, ACTION_RUN_WITH_RDMD);
797 
798         tb = res.getOrAddToolbar("Edit");
799         tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR,
800                       ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT);
801         tb = res.getOrAddToolbar("Debug");
802         tb.addButtons(ACTION_DEBUG_STOP, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_PAUSE,
803                       ACTION_DEBUG_RESTART,
804                       ACTION_DEBUG_STEP_INTO,
805                       ACTION_DEBUG_STEP_OVER,
806                       ACTION_DEBUG_STEP_OUT,
807                       );
808         return res;
809     }
810 
811     /// override to handle specific actions state (e.g. change enabled state for supported actions)
812     override bool handleActionStateRequest(const Action a) {
813         switch (a.id) {
814             case IDEActions.EditPreferences:
815                 return true;
816             case IDEActions.FileExit:
817             case IDEActions.FileOpen:
818             case IDEActions.WindowCloseDocument:
819             case IDEActions.WindowCloseAllDocuments:
820             case IDEActions.FileOpenWorkspace:
821                 // disable when background operation in progress
822                 if (!_currentBackgroundOperation)
823                     a.state = ACTION_STATE_ENABLED;
824                 else
825                     a.state = ACTION_STATE_DISABLE;
826                 return true;
827             case IDEActions.HelpAbout:
828             case StandardAction.OpenUrl:
829                 // always enabled
830                 a.state = ACTION_STATE_ENABLED;
831                 return true;
832             case IDEActions.BuildProject:
833             case IDEActions.BuildWorkspace:
834             case IDEActions.RebuildProject:
835             case IDEActions.RebuildWorkspace:
836             case IDEActions.CleanProject:
837             case IDEActions.CleanWorkspace:
838             case IDEActions.UpdateProjectDependencies:
839             case IDEActions.RefreshProject:
840             case IDEActions.SetStartupProject:
841             case IDEActions.ProjectSettings:
842             case IDEActions.RevealProjectInExplorer:
843                 // enable when project exists
844                 if (currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
845                     a.state = ACTION_STATE_ENABLED;
846                 else
847                     a.state = ACTION_STATE_DISABLE;
848                 return true;
849             case IDEActions.RunWithRdmd:
850                 // enable when D source file is in current tab
851                 if (currentEditor && !_currentBackgroundOperation && currentEditor.id.endsWith(".d"))
852                     a.state = ACTION_STATE_ENABLED;
853                 else
854                     a.state = ACTION_STATE_DISABLE;
855                 return true;
856             case IDEActions.DebugStop:
857                 a.state = isExecutionActive ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
858                 return true;
859             case IDEActions.DebugStart:
860             case IDEActions.DebugStartNoDebug:
861                 if (!isExecutionActive && currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
862                     a.state = ACTION_STATE_ENABLED;
863                 else
864                     a.state = ACTION_STATE_DISABLE;
865                 return true;
866             case IDEActions.DebugContinue:
867             case IDEActions.DebugPause:
868             case IDEActions.DebugStepInto:
869             case IDEActions.DebugStepOver:
870             case IDEActions.DebugStepOut:
871             case IDEActions.DebugRestart:
872                 if (_debugHandler)
873                     return _debugHandler.handleActionStateRequest(a);
874                 else
875                     a.state = ACTION_STATE_DISABLE;
876                 return true;
877             default:
878                 return super.handleActionStateRequest(a);
879         }
880     }
881 
882     FileDialog createFileDialog(UIString caption) {
883         FileDialog dlg = new FileDialog(caption, window, null);
884         dlg.filetypeIcons[".d"] = "text-d";
885         dlg.filetypeIcons["dub.json"] = "project-d";
886         dlg.filetypeIcons["dub.sdl"] = "project-d";
887         dlg.filetypeIcons["package.json"] = "project-d";
888         dlg.filetypeIcons[".dlangidews"] = "project-development";
889         return dlg;
890     }
891 
892     /// override to handle specific actions
893     override bool handleAction(const Action a) {
894         if (a) {
895             switch (a.id) {
896                 case IDEActions.FileExit:
897                     if (onCanClose())
898                         window.close();
899                     return true;
900                 case IDEActions.HelpViewHelp:
901                     Platform.instance.openURL(HELP_PAGE_URL);
902                     return true;
903                 case IDEActions.HelpAbout:
904                     //debug {
905                     //    testDCDFailAfterThreadCreation();
906                     //}
907                     window.showMessageBox(UIString.fromId("ABOUT"c) ~ " " ~ DLANGIDE_VERSION,
908                                           UIString.fromRaw("DLangIDE\n(C) Vadim Lopatin, 2014-2017\nhttp://github.com/buggins/dlangide\nIDE for D programming language written in D\nUses DlangUI library for GUI"d));
909                     return true;
910                 case StandardAction.OpenUrl:
911                     platform.openURL(a.stringParam);
912                     return true;
913                 case IDEActions.FileOpen:
914                     UIString caption;
915                     caption = UIString.fromId("HEADER_OPEN_TEXT_FILE"c);
916                     FileDialog dlg = createFileDialog(caption);
917                     dlg.addFilter(FileFilterEntry(UIString.fromId("SOURCE_FILES"c), "*.d;*.dd;*.ddoc;*.di;*.dt;*.dh;*.json;*.sdl;*.xml;*.ini"));
918                     dlg.addFilter(FileFilterEntry(UIString.fromId("ALL_FILES"c), "*.*"));
919                     dlg.path = _settings.getRecentPath("FILE_OPEN_PATH");
920                     dlg.dialogResult = delegate(Dialog d, const Action result) {
921                         if (result.id == ACTION_OPEN.id) {
922                             string filename = result.stringParam;
923                             openSourceFile(filename);
924                             _settings.setRecentPath(dlg.path, "FILE_OPEN_PATH");
925                         }
926                     };
927                     dlg.show();
928                     return true;
929                 case IDEActions.BuildProject:
930                 case IDEActions.BuildWorkspace:
931                     buildProject(BuildOperation.Build, cast(Project)a.objectParam);
932                     return true;
933                 case IDEActions.RebuildProject:
934                 case IDEActions.RebuildWorkspace:
935                     buildProject(BuildOperation.Rebuild, cast(Project)a.objectParam);
936                     return true;
937                 case IDEActions.CleanProject:
938                 case IDEActions.CleanWorkspace:
939                     buildProject(BuildOperation.Clean, cast(Project)a.objectParam);
940                     return true;
941                 case IDEActions.RunWithRdmd:
942                     runWithRdmd(currentEditor.id);
943                     return true;
944                 case IDEActions.DebugStartNoDebug:
945                     buildAndRunProject(cast(Project)a.objectParam);
946                     return true;
947                 case IDEActions.DebugStart:
948                     buildAndDebugProject(cast(Project)a.objectParam);
949                     return true;
950                 case IDEActions.DebugPause:
951                 case IDEActions.DebugStepInto:
952                 case IDEActions.DebugStepOver:
953                 case IDEActions.DebugStepOut:
954                 case IDEActions.DebugRestart:
955                     if (_debugHandler)
956                         return _debugHandler.handleAction(a);
957                     return true;
958                 case IDEActions.DebugContinue:
959                     if (_debugHandler)
960                         return _debugHandler.handleAction(a);
961                     else
962                         buildAndRunProject(cast(Project)a.objectParam);
963                     return true;
964                 case IDEActions.DebugStop:
965                     if (_debugHandler)
966                         return _debugHandler.handleAction(a);
967                     else
968                         stopExecution();
969                     return true;
970                 case IDEActions.UpdateProjectDependencies:
971                     buildProject(BuildOperation.Upgrade, cast(Project)a.objectParam);
972                     return true;
973                 case IDEActions.RefreshProject:
974                     refreshWorkspace();
975                     return true;
976                 case IDEActions.RevealProjectInExplorer:
977                     revealProjectInExplorer(cast(Project)a.objectParam);
978                     return true;
979                 case IDEActions.WindowCloseDocument:
980                     onTabClose(_tabs.selectedTabId);
981                     return true;
982                 case IDEActions.WindowCloseAllDocuments:
983                     askForUnsavedEdits(delegate() {
984                         closeAllDocuments();
985                     });
986                     return true;
987                 case IDEActions.FileOpenWorkspace:
988                     // Already specified workspace
989                     if (!a.stringParam.empty) {
990                         openFileOrWorkspace(a.stringParam);
991                         return true;
992                     }
993                     // Ask user for workspace to open
994                     UIString caption = UIString.fromId("HEADER_OPEN_WORKSPACE_OR_PROJECT"c);
995                     FileDialog dlg = createFileDialog(caption);
996                     dlg.addFilter(FileFilterEntry(UIString.fromId("WORKSPACE_AND_PROJECT_FILES"c), "*.dlangidews;dub.json;dub.sdl;package.json"));
997                     dlg.path = _settings.getRecentPath("FILE_OPEN_WORKSPACE_PATH");
998                     dlg.dialogResult = delegate(Dialog d, const Action result) {
999                         if (result.id == ACTION_OPEN.id) {
1000                             string filename = result.stringParam;
1001                             if (filename.length) {
1002                                 openFileOrWorkspace(filename);
1003                                 _settings.setRecentPath(dlg.path, "FILE_OPEN_WORKSPACE_PATH");
1004                             }
1005                         }
1006                     };
1007                     dlg.show();
1008                     return true;
1009                 case IDEActions.GoToDefinition:
1010                     if (currentEditor) {
1011                         Log.d("Trying to go to definition.");
1012                         currentEditor.editorTool.goToDefinition(currentEditor(), currentEditor.caretPos);
1013                     }
1014                     return true;
1015                 case IDEActions.GetDocComments:
1016                     Log.d("Trying to get doc comments.");
1017                     currentEditor.editorTool.getDocComments(currentEditor, currentEditor.caretPos, delegate(string[] results) {
1018                         if (results.length)
1019                             currentEditor.showDocCommentsPopup(results);
1020                     });
1021                     return true;
1022                 case IDEActions.GetParenCompletion:
1023                     Log.d("Trying to get paren completion.");
1024                     //auto results = currentEditor.editorTool.getParenCompletion(currentEditor, currentEditor.caretPos);
1025                     return true;
1026                 case IDEActions.GetCompletionSuggestions:
1027                     Log.d("Getting auto completion suggestions.");
1028                     currentEditor.editorTool.getCompletions(currentEditor, currentEditor.caretPos, delegate(dstring[] results, string[] icons) {
1029                         if (currentEditor)
1030                             currentEditor.showCompletionPopup(results, icons);
1031                     });
1032                     return true;
1033                 case IDEActions.EditPreferences:
1034                     showPreferences();
1035                     return true;
1036                 case IDEActions.ProjectSettings:
1037                     showProjectSettings(cast(Project)a.objectParam);
1038                     return true;
1039                 case IDEActions.SetStartupProject:
1040                     setStartupProject(cast(Project)a.objectParam);
1041                     return true;
1042                 case IDEActions.FindInFiles:
1043                     Log.d("Opening Search Field");
1044                        import dlangide.ui.searchPanel;
1045                     int searchPanelIndex = _logPanel.getTabs.tabIndex("search");
1046                     SearchWidget searchPanel = null;
1047                     if(searchPanelIndex == -1) {
1048                         searchPanel = new SearchWidget("search", this);
1049                         _logPanel.getTabs.addTab( searchPanel, "Search"d, null, true);
1050                     }
1051                     else {
1052                         searchPanel = cast(SearchWidget) _logPanel.getTabs.tabBody(searchPanelIndex);
1053                     }
1054                     _logPanel.getTabs.selectTab("search");
1055                     if(searchPanel !is null) { 
1056                         searchPanel.focus();
1057                         dstring selectedText = currentEditor.getSelectedText();
1058                         searchPanel.setSearchText(selectedText);
1059                     }
1060                     return true;
1061                 case IDEActions.FileNewWorkspace:
1062                     createNewProject(true);
1063                     return true;
1064                 case IDEActions.FileNewProject:
1065                     createNewProject(false);
1066                     return true;
1067                 case IDEActions.FileNew:
1068                     addProjectItem(a.objectParam);
1069                     return true;
1070                 case IDEActions.ProjectFolderRemoveItem:
1071                     removeProjectItem(a.objectParam);
1072                     return true;
1073                 case IDEActions.ProjectFolderRefresh:
1074                     refreshProjectItem(a.objectParam);
1075                     return true;
1076                 case IDEActions.CloseWorkspace:
1077                     closeWorkspace();
1078                     return true;
1079                 default:
1080                     return super.handleAction(a);
1081             }
1082         }
1083         return false;
1084     }
1085 
1086     @property ProjectSourceFile currentEditorSourceFile() {
1087         TabItem tab = _tabs.selectedTab;
1088         if (tab) {
1089             return cast(ProjectSourceFile)tab.objectParam;
1090         }
1091         return null;
1092     }
1093 
1094     void closeWorkspace() {
1095         if (currentWorkspace)
1096             currentWorkspace.save();
1097         askForUnsavedEdits(delegate() {
1098             setWorkspace(null);
1099             showHomeScreen();
1100         });
1101     }
1102 
1103     void onBreakpointListChanged(ProjectSourceFile sourcefile, Breakpoint[] breakpoints) {
1104         if (!currentWorkspace)
1105             return;
1106         if (sourcefile) {
1107             currentWorkspace.setSourceFileBreakpoints(sourcefile, breakpoints);
1108         }
1109         if (_debugHandler)
1110             _debugHandler.onBreakpointListUpdated(currentWorkspace.getBreakpoints());
1111     }
1112 
1113     void onBookmarkListChanged(ProjectSourceFile sourcefile, EditorBookmark[] bookmarks) {
1114         if (!currentWorkspace)
1115             return;
1116         if (sourcefile)
1117             currentWorkspace.setSourceFileBookmarks(sourcefile, bookmarks);
1118     }
1119 
1120     void refreshProjectItem(const Object obj) {
1121         if (currentWorkspace is null)
1122             return;
1123         Project project;
1124         ProjectFolder folder;
1125         if (cast(Workspace)obj) {
1126             Workspace ws = cast(Workspace)obj;
1127             ws.refresh();
1128             refreshWorkspace();
1129         } else if (cast(Project)obj) {
1130             project = cast(Project)obj;
1131         } else if (cast(ProjectFolder)obj) {
1132             folder = cast(ProjectFolder)obj;
1133             project = folder.project;
1134         } else if (cast(ProjectSourceFile)obj) {
1135             ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1136             folder = cast(ProjectFolder)srcfile.parent;
1137             project = srcfile.project;
1138         } else {
1139             ProjectSourceFile srcfile = currentEditorSourceFile;
1140             if (srcfile) {
1141                 folder = cast(ProjectFolder)srcfile.parent;
1142                 project = srcfile.project;
1143             }
1144         }
1145         if (project) {
1146             project.refresh();
1147             refreshWorkspace();
1148         }
1149     }
1150 
1151     void removeProjectItem(const Object obj) {
1152         if (currentWorkspace is null)
1153             return;
1154         ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1155         if (!srcfile)
1156             return;
1157         Project project = srcfile.project;
1158         if (!project)
1159             return;
1160         window.showMessageBox(UIString.fromRaw("Remove file"d), 
1161                 UIString.fromRaw("Do you want to remove file "d ~ srcfile.name ~ "?"), 
1162                 [ACTION_YES, ACTION_NO], 
1163                 1, delegate(const Action result) {
1164                     if (result == StandardAction.Yes) {
1165                         // save and close
1166                         try {
1167                             import std.file : remove;
1168                             closeTab(srcfile.filename);
1169                             remove(srcfile.filename);
1170                             project.refresh();
1171                             refreshWorkspace();
1172                         } catch (Exception e) {
1173                             Log.e("Error while removing file");
1174                         }
1175                     }
1176                     // else ignore
1177                     return true;
1178                 });
1179 
1180     }
1181 
1182     void addProjectItem(const Object obj) {
1183         if (currentWorkspace is null)
1184             return;
1185         Project project;
1186         ProjectFolder folder;
1187         if (cast(Project)obj) {
1188             project = cast(Project)obj;
1189         } else if (cast(ProjectFolder)obj) {
1190             folder = cast(ProjectFolder)obj;
1191             project = folder.project;
1192         } else if (cast(ProjectSourceFile)obj) {
1193             ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1194             folder = cast(ProjectFolder)srcfile.parent;
1195             project = srcfile.project;
1196         } else {
1197             ProjectSourceFile srcfile = currentEditorSourceFile;
1198             if (srcfile) {
1199                 folder = cast(ProjectFolder)srcfile.parent;
1200                 project = srcfile.project;
1201             }
1202         }
1203         if (project && folder && project.workspace is currentWorkspace) {
1204             NewFileDlg dlg = new NewFileDlg(this, project, folder);
1205             dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1206                 if (result.id == ACTION_FILE_NEW_SOURCE_FILE.id) {
1207                     FileCreationResult res = cast(FileCreationResult)result.objectParam;
1208                     if (res) {
1209                         //res.project.reload();
1210                         res.project.refresh();
1211                         refreshWorkspace();
1212                         if (isSupportedSourceTextFileFormat(res.filename)) {
1213                             openSourceFile(res.filename, null, true);
1214                         }
1215                     }
1216                 }
1217             };
1218             dlg.show();
1219         }
1220     }
1221 
1222     void createNewProject(bool newWorkspace) {
1223         if (currentWorkspace is null)
1224             newWorkspace = true;
1225         string location = _settings.getRecentPath("FILE_OPEN_WORKSPACE_PATH");
1226         if (newWorkspace && location)
1227             location = location.dirName;
1228         NewProjectDlg dlg = new NewProjectDlg(this, newWorkspace, currentWorkspace, location);
1229         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1230             if (result.id == ACTION_FILE_NEW_PROJECT.id || result.id == ACTION_FILE_NEW_WORKSPACE.id) {
1231                 //Log.d("settings after edit:\n", s.toJSON(true));
1232                 ProjectCreationResult res = cast(ProjectCreationResult)result.objectParam;
1233                 if (res) {
1234                     // open workspace/project
1235                     if (currentWorkspace is null || res.workspace !is currentWorkspace) {
1236                         // open new workspace
1237                         setWorkspace(res.workspace);
1238                         refreshWorkspace();
1239                         hideHomeScreen();
1240                     } else {
1241                         // project added to current workspace
1242                         loadProject(res.project);
1243                         refreshWorkspace();
1244                         hideHomeScreen();
1245                     }
1246                 }
1247             }
1248         };
1249         dlg.show();
1250     }
1251 
1252     void showPreferences() {
1253         //Log.d("settings before copy:\n", _settings.setting.toJSON(true));
1254         Setting s = _settings.copySettings();
1255         //Log.d("settings after copy:\n", s.toJSON(true));
1256         SettingsDialog dlg = new SettingsDialog(UIString.fromId("HEADER_SETTINGS"c), window, s, createSettingsPages());
1257         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1258             if (result.id == ACTION_APPLY.id) {
1259                 //Log.d("settings after edit:\n", s.toJSON(true));
1260                 _settings.applySettings(s);
1261                 applySettings(_settings);
1262                 _settings.save();
1263             }
1264         };
1265         dlg.show();
1266     }
1267 
1268     void setStartupProject(Project project) {
1269         if (!currentWorkspace)
1270             return;
1271         if (!project)
1272             return;
1273         currentWorkspace.startupProject = project;
1274         if (_wsPanel)
1275             _wsPanel.updateDefault();
1276     }
1277 
1278     void showProjectSettings(Project project) {
1279         if (!currentWorkspace)
1280             return;
1281         if (!project)
1282             project = currentWorkspace.startupProject;
1283         if (!project)
1284             return;
1285         Setting s = project.settings.copySettings();
1286         SettingsDialog dlg = new SettingsDialog(UIString.fromRaw(project.name ~ " settings"d), window, s, createProjectSettingsPages());
1287         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1288             if (result.id == ACTION_APPLY.id) {
1289                 //Log.d("settings after edit:\n", s.toJSON(true));
1290                 project.settings.applySettings(s);
1291                 project.settings.save();
1292             }
1293         };
1294         dlg.show();
1295     }
1296 
1297     void applySettings(IDESettings settings) {
1298         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
1299             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
1300             if (ed) {
1301                 applySettings(ed, settings);
1302             }
1303         }
1304         FontManager.fontGamma = settings.fontGamma;
1305         FontManager.hintingMode = settings.hintingMode;
1306         FontManager.minAnitialiasedFontSize = settings.minAntialiasedFontSize;
1307         Platform.instance.uiLanguage = settings.uiLanguage;
1308         Platform.instance.uiTheme = settings.uiTheme;
1309         requestLayout();
1310     }
1311 
1312     void applySettings(DSourceEdit editor, IDESettings settings) {
1313         editor.settings(settings).applySettings();
1314     }
1315 
1316     private bool loadProject(Project project) {
1317         if (!project.load()) {
1318             _logPanel.logLine("Cannot read project " ~ project.filename);
1319             window.showMessageBox(UIString.fromId("ERROR_OPEN_PROJECT"c).value, UIString.fromId("ERROR_OPENING_PROJECT"c).value ~ toUTF32(project.filename));
1320             return false;
1321         }
1322         const auto msg = UIString.fromId("MSG_OPENED_PROJECT"c);
1323         _logPanel.logLine(toUTF32("Project file " ~ project.filename ~  " is opened ok"));
1324         return true;
1325     }
1326 
1327     void openFileOrWorkspace(string filename) {
1328         // Open DlangIDE workspace file
1329         if (filename.isWorkspaceFile) {
1330             Workspace ws = new Workspace(this);
1331             if (ws.load(filename)) {
1332                     askForUnsavedEdits(delegate() {
1333                     setWorkspace(ws);
1334                     hideHomeScreen();
1335                     _settings.updateRecentWorkspace(filename);
1336                 });
1337             } else {
1338                 window.showMessageBox(UIString.fromId("ERROR_OPEN_WORKSPACE"c).value, UIString.fromId("ERROR_OPENING_WORKSPACE"c).value);
1339                 return;
1340             }
1341         } else if (filename.isProjectFile) { // Open non-DlangIDE project file or DlangIDE project
1342             _logPanel.clear();
1343             const auto msg = UIString.fromId("MSG_TRY_OPEN_PROJECT"c).value;
1344             _logPanel.logLine(msg ~ toUTF32(" " ~ filename));
1345             Project project = new Project(currentWorkspace, filename);
1346             string defWsFile = project.defWorkspaceFile;
1347             if (currentWorkspace) {
1348                 Project existing = currentWorkspace.findProject(project.filename);
1349                 if (existing) {
1350                     _logPanel.logLine("Project is already in workspace"d);
1351                     window.showMessageBox(UIString.fromId("MSG_OPEN_PROJECT"c), UIString.fromId("MSG_PROJECT_ALREADY_OPENED"c));
1352                     return;
1353                 }
1354                 window.showMessageBox(UIString.fromId("MSG_OPEN_PROJECT"c), UIString.fromId("QUESTION_NEW_WORKSPACE"c),
1355 
1356                                       [ACTION_ADD_TO_CURRENT_WORKSPACE, ACTION_CREATE_NEW_WORKSPACE, ACTION_CANCEL], 0, delegate(const Action result) {
1357                                           if (result.id == IDEActions.CreateNewWorkspace) {
1358                                               // new ws
1359                                               createNewWorkspaceForExistingProject(project);
1360                                               hideHomeScreen();
1361                                           } else if (result.id == IDEActions.AddToCurrentWorkspace) {
1362                                               // add to current
1363                                               currentWorkspace.addProject(project);
1364                                               loadProject(project);
1365                                               currentWorkspace.save();
1366                                               refreshWorkspace();
1367                                               hideHomeScreen();
1368                                           }
1369                                           return true;
1370                                       });
1371             } else {
1372                 // new workspace file
1373                 createNewWorkspaceForExistingProject(project);
1374             }
1375         } else {
1376             _logPanel.logLine("File is not recognized as DlangIDE project or workspace file");
1377             window.showMessageBox(UIString.fromId("ERROR_INVALID_WORKSPACE_FILE"c), UIString.fromId("ERROR_INVALID_WS_OR_PROJECT_FILE"c));
1378         }
1379     }
1380 
1381     void refreshWorkspace() {
1382         _logPanel.logLine("Refreshing workspace");
1383         _wsPanel.reloadItems();
1384         closeRemovedDocuments();
1385     }
1386 
1387     void createNewWorkspaceForExistingProject(Project project) {
1388         string defWsFile = project.defWorkspaceFile;
1389         _logPanel.logLine("Creating new workspace " ~ defWsFile);
1390         // new ws
1391         Workspace ws = new Workspace(this);
1392         ws.name = project.name;
1393         ws.description = project.description;
1394         Log.d("workspace name: ", project.name);
1395         Log.d("workspace description: ", project.description);
1396         ws.addProject(project);
1397         // Load project data
1398         loadProject(project);
1399         ws.save(defWsFile);
1400         setWorkspace(ws);
1401         _logPanel.logLine("Done");
1402     }
1403 
1404     //bool loadWorkspace(string path) {
1405     //    // testing workspace loader
1406     //    Workspace ws = new Workspace();
1407     //    ws.load(path);
1408     //    setWorkspace(ws);
1409     //    //ws.save(ws.filename ~ ".bak");
1410     //    return true;
1411     //}
1412 
1413     void setWorkspace(Workspace ws) {
1414         closeAllDocuments();
1415         currentWorkspace = ws;
1416         _wsPanel.workspace = ws;
1417         requestActionsUpdate();
1418         if (ws && ws.startupProject && ws.startupProject.mainSourceFile) {
1419             openSourceFile(ws.startupProject.mainSourceFile.filename);
1420             _tabs.setFocus();
1421         }
1422         if (ws) {
1423             _settings.updateRecentWorkspace(ws.filename);
1424             _settings.setRecentPath(ws.dir, "FILE_OPEN_WORKSPACE_PATH");
1425         }
1426 
1427     }
1428 
1429     void refreshProject(Project project) {
1430         if (currentWorkspace && project.loadSelections()) {
1431             currentWorkspace.cleanupUnusedDependencies();
1432             refreshWorkspace();
1433         }
1434     }
1435 
1436     void revealProjectInExplorer(Project project) {
1437         Platform.instance.showInFileManager(project.items.filename);
1438     }
1439 
1440     void buildProject(BuildOperation buildOp, Project project, BuildResultListener listener = null) {
1441         if (!currentWorkspace) {
1442             _logPanel.logLine("No workspace is opened");
1443             return;
1444         }
1445         if (!project)
1446             project = currentWorkspace.startupProject;
1447         if (!project) {
1448             _logPanel.logLine("No project is opened");
1449             return;
1450         }
1451         _logPanel.activateLogTab();
1452         if (!listener) {
1453             if (buildOp == BuildOperation.Upgrade || buildOp == BuildOperation.Build || buildOp == BuildOperation.Rebuild) {
1454                 listener = delegate(int result) {
1455                     if (!result) {
1456                         // success: update workspace
1457                         refreshProject(project);
1458                     } else {
1459                         handleBuildError(result, project);
1460                     }
1461                 };
1462             }
1463         }
1464         ProjectSettings projectSettings = project.settings;
1465         string toolchain = projectSettings.getToolchain(_settings);
1466         string arch = projectSettings.getArch(_settings);
1467         string dubExecutable = _settings.dubExecutable;
1468         string dubAdditionalParams = projectSettings.getDubAdditionalParams(_settings);
1469         Builder op = new Builder(this, project, _logPanel, currentWorkspace.projectConfiguration, currentWorkspace.buildConfiguration, buildOp, 
1470                                  dubExecutable, dubAdditionalParams,
1471                                  toolchain,
1472                                  arch,
1473                                  listener);
1474         setBackgroundOperation(op);
1475     }
1476     
1477     /// updates list of available configurations
1478     void setProjectConfigurations(dstring[] items) {
1479         projectConfigurationCombo.items = items;
1480     }
1481     
1482     /// handle files dropped to application window
1483     void onFilesDropped(string[] filenames) {
1484         //Log.d("onFilesDropped(", filenames, ")");
1485         bool first = true;
1486         for (int i = 0; i < filenames.length; i++) {
1487             openSourceFile(filenames[i], null, first);
1488             first = false;
1489         }
1490     }
1491 
1492     /// return false to prevent closing
1493     bool onCanClose() {
1494         askForUnsavedEdits(delegate() {
1495             if (currentWorkspace)
1496                 currentWorkspace.save();
1497             window.close();
1498         });
1499         return false;
1500     }
1501     /// called when main window is closing
1502     void onWindowClose() {
1503         Log.i("onWindowClose()");
1504         stopExecution();
1505     }
1506 }
1507