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