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 // TODO: get version from GIT commit
48 //version is now stored in file views/VERSION
49 immutable dstring DLANGIDE_VERSION = toUTF32(import("VERSION"));
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.fromId("ERROR_CANNOT_DEBUG_PROJECT"c), UIString.fromId("ERROR_STARTUP_PROJECT_ABSENT"c));
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                 _tabs.layout(_tabs.pos);
452             } else {
453                 destroy(editor);
454                 if (window)
455                     window.showMessageBox(UIString.fromId("ERROR_OPEN_FILE"c), UIString.fromId("ERROR_OPENING_FILE"c) ~ " " ~ toUTF32(file.filename));
456                 return false;
457             }
458         }
459         if (activate) {
460             focusEditor(filename);
461         }
462         requestLayout();
463         return true;
464     }
465 
466     static immutable HOME_SCREEN_ID = "HOME_SCREEN";
467     void showHomeScreen() {
468         int index = _tabs.tabIndex(HOME_SCREEN_ID);
469         if (index >= 0) {
470             _tabs.selectTab(index, true);
471         } else {
472             HomeScreen home = new HomeScreen(HOME_SCREEN_ID, this);
473             _tabs.addTab(home, UIString.fromId("HOME"c), null, true);
474             _tabs.selectTab(HOME_SCREEN_ID, true);
475              auto _settings = new IDESettings(buildNormalizedPath(settingsDir, "settings.json"));
476             // Auto open last workspace, if no workspace specified in command line and autoOpen flag set to true
477             const auto recentWorkspaces = settings.recentWorkspaces;
478             if (!openedWorkspace && recentWorkspaces.length > 0 && _settings.autoOpenLastProject())
479             {
480                 Action a = ACTION_FILE_OPEN_WORKSPACE.clone();
481                 a.stringParam = recentWorkspaces[0];
482                 handleAction(a);
483             }
484         }
485     }
486 
487     void hideHomeScreen() {
488         _tabs.removeTab(HOME_SCREEN_ID);
489     }
490 
491     void onTabChanged(string newActiveTabId, string previousTabId) {
492         int index = _tabs.tabIndex(newActiveTabId);
493         if (index >= 0) {
494             TabItem tab = _tabs.tab(index);
495             ProjectSourceFile file = cast(ProjectSourceFile)tab.objectParam;
496             if (file) {
497                 //setCurrentProject(file.project);
498                 // tab is source file editor
499                 _wsPanel.selectItem(file);
500                 focusEditor(file.filename);
501             }
502             window.windowCaption(tab.text.value ~ " - "d ~ frameWindowCaptionSuffix);
503         }
504         requestActionsUpdate();
505     }
506 
507     // returns DSourceEdit from currently active tab (if it's editor), null if current tab is not editor or no tabs open
508     DSourceEdit currentEditor() {
509         return cast(DSourceEdit)_tabs.selectedTabBody();
510     }
511 
512     /// close tab w/o confirmation
513     void closeTab(string tabId) {
514         _wsPanel.selectItem(null);
515         _tabs.removeTab(tabId);
516     }
517 
518     void renameTab(string oldfilename, string newfilename) {
519         int index = _tabs.tabIndex(newfilename);
520         if (index >= 0) {
521             // file is already opened in tab - close it
522             _tabs.removeTab(newfilename);
523         }
524         int oldindex = _tabs.tabIndex(oldfilename);
525         if (oldindex >= 0) {
526             _tabs.renameTab(oldindex, newfilename, UIString.fromRaw(newfilename.baseName));
527         }
528     }
529 
530     /// close all editor tabs
531     void closeAllDocuments() {
532         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
533             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
534             if (ed) {
535                 closeTab(ed.id);
536             }
537         }
538     }
539 
540     /// returns array of all opened source editors
541     DSourceEdit[] allOpenedEditors() {
542         DSourceEdit[] res;
543         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
544             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
545             if (ed) {
546                 res ~= ed;
547             }
548         }
549         return res;
550     }
551 
552     /// close editor tabs for which files are removed from filesystem
553     void closeRemovedDocuments() {
554         import std.file;
555         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
556             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
557             if (ed) {
558                 if (!exists(ed.id) || !isFile(ed.id)) {
559                     closeTab(ed.id);
560                 }
561             }
562         }
563     }
564 
565     /// returns first unsaved document
566     protected DSourceEdit hasUnsavedEdits() {
567         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
568             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
569             if (ed && ed.content.modified) {
570                 return ed;
571             }
572         }
573         return null;
574     }
575 
576     protected void askForUnsavedEdits(void delegate() onConfirm) {
577         DSourceEdit ed = hasUnsavedEdits();
578         if (!ed) {
579             // no unsaved edits
580             onConfirm();
581             return;
582         }
583         string tabId = ed.id;
584         // tab content is modified - ask for confirmation
585         auto header = UIString.fromRaw("HEADER_CLOSE_FILE"c);
586         window.showMessageBox(header ~ " " ~ toUTF32(baseName(tabId)), UIString.fromId("MSG_FILE_CONTENT_CHANGED"c), 
587                               [ACTION_SAVE, ACTION_SAVE_ALL, ACTION_DISCARD_CHANGES, ACTION_DISCARD_ALL, ACTION_CANCEL], 
588                               0, delegate(const Action result) {
589                                   if (result == StandardAction.Save) {
590                                       // save and close
591                                       ed.save();
592                                       askForUnsavedEdits(onConfirm);
593                                   } else if (result == StandardAction.DiscardChanges) {
594                                       // close, don't save
595                                       closeTab(tabId);
596                                       closeAllDocuments();
597                                       onConfirm();
598                                   } else if (result == StandardAction.SaveAll) {
599                                       ed.save();
600                                       for(;;) {
601                                           DSourceEdit editor = hasUnsavedEdits();
602                                           if (!editor)
603                                               break;
604                                           editor.save();
605                                       }
606                                       closeAllDocuments();
607                                       onConfirm();
608                                   } else if (result == StandardAction.DiscardAll) {
609                                       // close, don't save
610                                       closeAllDocuments();
611                                       onConfirm();
612                                   }
613                                   // else ignore
614                                   return true;
615                               });
616     }
617 
618     protected void onTabClose(string tabId) {
619         Log.d("onTabClose ", tabId);
620         int index = _tabs.tabIndex(tabId);
621         if (index >= 0) {
622             DSourceEdit d = cast(DSourceEdit)_tabs.tabBody(tabId);
623             if (d && d.content.modified) {
624                 // tab content is modified - ask for confirmation
625                 window.showMessageBox(UIString.fromId("HEADER_CLOSE_TAB"c), UIString.fromId("MSG_TAB_CONTENT_CHANGED"c) ~ ": " ~ toUTF32(baseName(tabId)), 
626                                       [ACTION_SAVE, ACTION_DISCARD_CHANGES, ACTION_CANCEL], 
627                                       0, delegate(const Action result) {
628                                           if (result == StandardAction.Save) {
629                                               // save and close
630                                               d.save();
631                                               closeTab(tabId);
632                                           } else if (result == StandardAction.DiscardChanges) {
633                                               // close, don't save
634                                               closeTab(tabId);
635                                           }
636                                           // else ignore
637                                           return true;
638                                       });
639             } else {
640                 closeTab(tabId);
641             }
642         }
643         requestActionsUpdate();
644     }
645 
646     /// create app body widget
647     override protected Widget createBody() {
648         _dockHost = new DockHost();
649 
650         //=============================================================
651         // Create body - Tabs
652 
653         // editor tabs
654         _tabs = new TabWidget("TABS");
655         _tabs.hiddenTabsVisibility = Visibility.Gone;
656         //_tabs.setStyles(STYLE_DOCK_HOST_BODY, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT);
657         _tabs.setStyles(STYLE_DOCK_WINDOW, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT, STYLE_DOCK_HOST_BODY);
658         _tabs.tabChanged = &onTabChanged;
659         _tabs.tabClose = &onTabClose;
660 
661         _dockHost.bodyWidget = _tabs;
662 
663         //=============================================================
664         // Create workspace docked panel
665         _wsPanel = new WorkspacePanel("workspace");
666         _wsPanel.sourceFileSelectionListener = &onSourceFileSelected;
667         _wsPanel.workspaceActionListener = &handleAction;
668         _wsPanel.dockAlignment = DockAlignment.Left;
669         _dockHost.addDockedWindow(_wsPanel);
670         _wsPanel.visibility = Visibility.Gone;
671 
672         _logPanel = new OutputPanel("output");
673         _logPanel.compilerLogIssueClickHandler = &onCompilerLogIssueClick;
674         _logPanel.appendText(null, "DlangIDE is started\nHINT: Try to open some DUB project\n"d);
675         string dubPath = findExecutablePath("dub");
676         string rdmdPath = findExecutablePath("rdmd");
677         string dmdPath = findExecutablePath("dmd");
678         string ldcPath = findExecutablePath("ldc2");
679         string gdcPath = findExecutablePath("gdc");
680         _logPanel.appendText(null, dubPath ? ("dub path: "d ~ toUTF32(dubPath) ~ "\n"d) : ("dub is not found! cannot build projects without DUB\n"d));
681         _logPanel.appendText(null, rdmdPath ? ("rdmd path: "d ~ toUTF32(rdmdPath) ~ "\n"d) : ("rdmd is not found!\n"d));
682         _logPanel.appendText(null, dmdPath ? ("dmd path: "d ~ toUTF32(dmdPath) ~ "\n"d) : ("dmd compiler is not found!\n"d));
683         _logPanel.appendText(null, ldcPath ? ("ldc path: "d ~ toUTF32(ldcPath) ~ "\n"d) : ("ldc compiler is not found!\n"d));
684         _logPanel.appendText(null, gdcPath ? ("gdc path: "d ~ toUTF32(gdcPath) ~ "\n"d) : ("gdc compiler is not found!\n"d));
685 
686         _dockHost.addDockedWindow(_logPanel);
687 
688         return _dockHost;
689     }
690 
691     /// create main menu
692     override protected MainMenu createMainMenu() {
693 
694         mainMenuItems = new MenuItem();
695         MenuItem fileItem = new MenuItem(new Action(1, "MENU_FILE"));
696         MenuItem fileNewItem = new MenuItem(new Action(1, "MENU_FILE_NEW"));
697         fileNewItem.add(ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT);
698         fileItem.add(fileNewItem);
699         fileItem.add(ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 
700                      ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_WORKSPACE_CLOSE, ACTION_FILE_EXIT);
701 
702         MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT"));
703         editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, 
704                      ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_FIND_TEXT, ACTION_EDITOR_TOGGLE_BOOKMARK);
705         MenuItem editItemAdvanced = new MenuItem(new Action(221, "MENU_EDIT_ADVANCED"));
706         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);
707         editItem.add(editItemAdvanced);
708 
709         editItem.add(ACTION_EDIT_PREFERENCES);
710 
711         MenuItem navItem = new MenuItem(new Action(21, "MENU_NAVIGATE"));
712         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);
713 
714         MenuItem projectItem = new MenuItem(new Action(21, "MENU_PROJECT"));
715         projectItem.add(ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, ACTION_PROJECT_SETTINGS);
716 
717         MenuItem buildItem = new MenuItem(new Action(22, "MENU_BUILD"));
718         buildItem.add(ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
719                      ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN,
720                      ACTION_RUN_WITH_RDMD);
721 
722         MenuItem debugItem = new MenuItem(new Action(23, "MENU_DEBUG"));
723         debugItem.add(ACTION_DEBUG_START, ACTION_DEBUG_START_NO_DEBUG, 
724                       ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE,
725                       ACTION_DEBUG_RESTART,
726                       ACTION_DEBUG_STEP_INTO,
727                       ACTION_DEBUG_STEP_OVER,
728                       ACTION_DEBUG_STEP_OUT,
729                       ACTION_DEBUG_TOGGLE_BREAKPOINT, ACTION_DEBUG_ENABLE_BREAKPOINT, ACTION_DEBUG_DISABLE_BREAKPOINT
730                       );
731 
732 
733         MenuItem windowItem = new MenuItem(new Action(3, "MENU_WINDOW"c));
734         //windowItem.add(new Action(30, "MENU_WINDOW_PREFERENCES"));
735         windowItem.add(ACTION_WINDOW_CLOSE_DOCUMENT, ACTION_WINDOW_CLOSE_ALL_DOCUMENTS, ACTION_WINDOW_SHOW_HOME_SCREEN);
736         MenuItem helpItem = new MenuItem(new Action(4, "MENU_HELP"c));
737         helpItem.add(ACTION_HELP_VIEW_HELP, ACTION_HELP_ABOUT, ACTION_HELP_DONATE);
738         mainMenuItems.add(fileItem);
739         mainMenuItems.add(editItem);
740         mainMenuItems.add(projectItem);
741         mainMenuItems.add(navItem);
742         mainMenuItems.add(buildItem);
743         mainMenuItems.add(debugItem);
744         //mainMenuItems.add(viewItem);
745         mainMenuItems.add(windowItem);
746         mainMenuItems.add(helpItem);
747 
748         MainMenu mainMenu = new MainMenu(mainMenuItems);
749         //mainMenu.backgroundColor = 0xd6dbe9;
750         return mainMenu;
751     }
752 
753     /// override it
754     override protected void updateShortcuts() {
755         if (applyShortcutsSettings()) {
756             Log.d("Shortcut actions loaded");
757         } else {
758             Log.d("Saving default shortcuts");
759             const(Action)[] actions;
760             actions ~= [
761                 ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, 
762                 ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, 
763                 ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, 
764                 ACTION_EDIT_PREFERENCES, 
765                 ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT, ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 
766                 ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_EXIT, 
767                 ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, 
768                 ACTION_PROJECT_SETTINGS, ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
769                 ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN, ACTION_RUN_WITH_RDMD,
770                 ACTION_DEBUG_START, ACTION_DEBUG_START_NO_DEBUG, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE,
771                 ACTION_DEBUG_RESTART,
772                 ACTION_DEBUG_STEP_INTO,
773                 ACTION_DEBUG_STEP_OVER,
774                 ACTION_DEBUG_STEP_OUT,
775                 ACTION_WINDOW_CLOSE_DOCUMENT, ACTION_WINDOW_CLOSE_ALL_DOCUMENTS, ACTION_HELP_ABOUT];
776             actions ~= STD_EDITOR_ACTIONS;
777             saveShortcutsSettings(actions);
778         }
779     }
780 
781     /// create app toolbars
782     override protected ToolBarHost createToolbars() {
783         ToolBarHost res = new ToolBarHost();
784         ToolBar tb;
785         tb = res.getOrAddToolbar("Standard");
786         tb.addButtons(ACTION_FILE_OPEN, ACTION_FILE_SAVE, ACTION_SEPARATOR);
787 
788         tb.addButtons(ACTION_DEBUG_START);
789         
790         projectConfigurationCombo = new ToolBarComboBox("projectConfig", [ProjectConfiguration.DEFAULT_NAME.to!dstring]);//Updateable
791         projectConfigurationCombo.itemClick = delegate(Widget source, int index) {
792             if (currentWorkspace) {
793                 currentWorkspace.setStartupProjectConfiguration(projectConfigurationCombo.selectedItem.to!string); 
794             }
795             return true;
796         };
797         projectConfigurationCombo.action = ACTION_PROJECT_CONFIGURATIONS;
798         tb.addControl(projectConfigurationCombo);
799         
800         ToolBarComboBox cbBuildConfiguration = new ToolBarComboBox("buildConfig", ["Debug"d, "Release"d, "Unittest"d]);
801         cbBuildConfiguration.itemClick = delegate(Widget source, int index) {
802             if (currentWorkspace && index < 3) {
803                 currentWorkspace.buildConfiguration = [BuildConfiguration.Debug, BuildConfiguration.Release, BuildConfiguration.Unittest][index];
804             }
805             return true;
806         };
807         cbBuildConfiguration.action = ACTION_BUILD_CONFIGURATIONS;
808         tb.addControl(cbBuildConfiguration);
809         tb.addButtons(ACTION_PROJECT_BUILD, ACTION_SEPARATOR, ACTION_RUN_WITH_RDMD);
810 
811         tb = res.getOrAddToolbar("Edit");
812         tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR,
813                       ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT);
814         tb = res.getOrAddToolbar("Debug");
815         tb.addButtons(ACTION_DEBUG_STOP, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_PAUSE,
816                       ACTION_DEBUG_RESTART,
817                       ACTION_DEBUG_STEP_INTO,
818                       ACTION_DEBUG_STEP_OVER,
819                       ACTION_DEBUG_STEP_OUT,
820                       );
821         return res;
822     }
823 
824     /// override to handle specific actions state (e.g. change enabled state for supported actions)
825     override bool handleActionStateRequest(const Action a) {
826         switch (a.id) {
827             case IDEActions.EditPreferences:
828                 return true;
829             case IDEActions.FileExit:
830             case IDEActions.FileOpen:
831             case IDEActions.WindowCloseDocument:
832             case IDEActions.WindowCloseAllDocuments:
833             case IDEActions.WindowShowHomeScreen:
834             case IDEActions.FileOpenWorkspace:
835                 // disable when background operation in progress
836                 if (!_currentBackgroundOperation)
837                     a.state = ACTION_STATE_ENABLED;
838                 else
839                     a.state = ACTION_STATE_DISABLE;
840                 return true;
841             case IDEActions.HelpAbout:
842             case StandardAction.OpenUrl:
843                 // always enabled
844                 a.state = ACTION_STATE_ENABLED;
845                 return true;
846             case IDEActions.BuildProject:
847             case IDEActions.BuildWorkspace:
848             case IDEActions.RebuildProject:
849             case IDEActions.RebuildWorkspace:
850             case IDEActions.CleanProject:
851             case IDEActions.CleanWorkspace:
852             case IDEActions.UpdateProjectDependencies:
853             case IDEActions.RefreshProject:
854             case IDEActions.SetStartupProject:
855             case IDEActions.ProjectSettings:
856             case IDEActions.RevealProjectInExplorer:
857                 // enable when project exists
858                 if (currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
859                     a.state = ACTION_STATE_ENABLED;
860                 else
861                     a.state = ACTION_STATE_DISABLE;
862                 return true;
863             case IDEActions.RunWithRdmd:
864                 // enable when D source file is in current tab
865                 if (currentEditor && !_currentBackgroundOperation && currentEditor.id.endsWith(".d"))
866                     a.state = ACTION_STATE_ENABLED;
867                 else
868                     a.state = ACTION_STATE_DISABLE;
869                 return true;
870             case IDEActions.DebugStop:
871                 a.state = isExecutionActive ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
872                 return true;
873             case IDEActions.DebugStart:
874             case IDEActions.DebugStartNoDebug:
875                 if (!isExecutionActive && currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation)
876                     a.state = ACTION_STATE_ENABLED;
877                 else
878                     a.state = ACTION_STATE_DISABLE;
879                 return true;
880             case IDEActions.DebugContinue:
881             case IDEActions.DebugPause:
882             case IDEActions.DebugStepInto:
883             case IDEActions.DebugStepOver:
884             case IDEActions.DebugStepOut:
885             case IDEActions.DebugRestart:
886                 if (_debugHandler)
887                     return _debugHandler.handleActionStateRequest(a);
888                 else
889                     a.state = ACTION_STATE_DISABLE;
890                 return true;
891             case IDEActions.FindInFiles:
892                 a.state = currentWorkspace !is null ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE;
893                 return true;
894             default:
895                 return super.handleActionStateRequest(a);
896         }
897     }
898 
899     FileDialog createFileDialog(UIString caption, int fileDialogFlags = DialogFlag.Modal | DialogFlag.Resizable | FileDialogFlag.FileMustExist) {
900         FileDialog dlg = new FileDialog(caption, window, null, fileDialogFlags);
901         dlg.filetypeIcons[".d"] = "text-d";
902         dlg.filetypeIcons["dub.json"] = "project-d";
903         dlg.filetypeIcons["dub.sdl"] = "project-d";
904         dlg.filetypeIcons["package.json"] = "project-d";
905         dlg.filetypeIcons[".dlangidews"] = "project-development";
906         return dlg;
907     }
908 
909     /// override to handle specific actions
910     override bool handleAction(const Action a) {
911         if (a) {
912             switch (a.id) {
913                 case IDEActions.FileExit:
914                     if (onCanClose())
915                         window.close();
916                     return true;
917                 case IDEActions.HelpViewHelp:
918                     Platform.instance.openURL(HELP_PAGE_URL);
919                     return true;
920                 case IDEActions.HelpDonate:
921                     Platform.instance.openURL(HELP_DONATION_URL);
922                     return true;
923                 case IDEActions.HelpAbout:
924                     //debug {
925                     //    testDCDFailAfterThreadCreation();
926                     //}
927                     window.showMessageBox(UIString.fromId("ABOUT"c) ~ " " ~ DLANGIDE_VERSION,
928                                           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));
929                     return true;
930                 case StandardAction.OpenUrl:
931                     platform.openURL(a.stringParam);
932                     return true;
933                 case IDEActions.FileSaveAs:
934                     DSourceEdit ed = currentEditor;
935                     UIString caption;
936                     caption = UIString.fromId("HEADER_SAVE_FILE_AS"c);
937                     FileDialog dlg = createFileDialog(caption, DialogFlag.Modal | DialogFlag.Resizable | FileDialogFlag.Save);
938                     dlg.addFilter(FileFilterEntry(UIString.fromId("SOURCE_FILES"c), "*.d;*.dd;*.ddoc;*.di;*.dt;*.dh;*.json;*.sdl;*.xml;*.ini"));
939                     dlg.addFilter(FileFilterEntry(UIString.fromId("ALL_FILES"c), "*.*"));
940                     dlg.path = ed.filename.dirName;
941                     dlg.filename = ed.filename;
942                     dlg.dialogResult = delegate(Dialog d, const Action result) {
943                         if (result.id == ACTION_SAVE.id) {
944                             string oldfilename = ed.filename;
945                             string filename = result.stringParam;
946                             ed.save(filename);
947                             if (oldfilename == filename)
948                                 return;
949                             renameTab(oldfilename, filename);
950                             ed.id = filename;
951                             ed.setSyntaxSupport();
952                             if( filename.endsWith(".d") || filename.endsWith(".di") )
953                                 ed.editorTool = new DEditorTool(this);
954                             else
955                                 ed.editorTool = new DefaultEditorTool(this);
956                             //openSourceFile(filename);
957                             refreshWorkspace();
958                             ProjectSourceFile file = _wsPanel.findSourceFileItem(filename, false);
959                             if (file) {
960                                 ed.projectSourceFile = file;
961                             } else
962                                 ed.projectSourceFile = null;
963                             _settings.setRecentPath(dlg.path, "FILE_OPEN_PATH");
964                         }
965                     };
966                     dlg.show();
967                     return true;
968                 case IDEActions.FileOpen:
969                     UIString caption;
970                     caption = UIString.fromId("HEADER_OPEN_TEXT_FILE"c);
971                     FileDialog dlg = createFileDialog(caption);
972                     dlg.addFilter(FileFilterEntry(UIString.fromId("SOURCE_FILES"c), "*.d;*.dd;*.ddoc;*.di;*.dt;*.dh;*.json;*.sdl;*.xml;*.ini"));
973                     dlg.addFilter(FileFilterEntry(UIString.fromId("ALL_FILES"c), "*.*"));
974                     dlg.path = _settings.getRecentPath("FILE_OPEN_PATH");
975                     dlg.dialogResult = delegate(Dialog d, const Action result) {
976                         if (result.id == ACTION_OPEN.id) {
977                             string filename = result.stringParam;
978                             openSourceFile(filename);
979                             _settings.setRecentPath(dlg.path, "FILE_OPEN_PATH");
980                         }
981                     };
982                     dlg.show();
983                     return true;
984                 case IDEActions.BuildProject:
985                 case IDEActions.BuildWorkspace:
986                     buildProject(BuildOperation.Build, cast(Project)a.objectParam);
987                     return true;
988                 case IDEActions.RebuildProject:
989                 case IDEActions.RebuildWorkspace:
990                     buildProject(BuildOperation.Rebuild, cast(Project)a.objectParam);
991                     return true;
992                 case IDEActions.CleanProject:
993                 case IDEActions.CleanWorkspace:
994                     buildProject(BuildOperation.Clean, cast(Project)a.objectParam);
995                     return true;
996                 case IDEActions.RunWithRdmd:
997                     runWithRdmd(currentEditor.id);
998                     return true;
999                 case IDEActions.DebugStartNoDebug:
1000                     buildAndRunProject(cast(Project)a.objectParam);
1001                     return true;
1002                 case IDEActions.DebugStart:
1003                     buildAndDebugProject(cast(Project)a.objectParam);
1004                     return true;
1005                 case IDEActions.DebugPause:
1006                 case IDEActions.DebugStepInto:
1007                 case IDEActions.DebugStepOver:
1008                 case IDEActions.DebugStepOut:
1009                 case IDEActions.DebugRestart:
1010                     if (_debugHandler)
1011                         return _debugHandler.handleAction(a);
1012                     return true;
1013                 case IDEActions.DebugContinue:
1014                     if (_debugHandler)
1015                         return _debugHandler.handleAction(a);
1016                     else
1017                         buildAndRunProject(cast(Project)a.objectParam);
1018                     return true;
1019                 case IDEActions.DebugStop:
1020                     if (_debugHandler)
1021                         return _debugHandler.handleAction(a);
1022                     else
1023                         stopExecution();
1024                     return true;
1025                 case IDEActions.UpdateProjectDependencies:
1026                     buildProject(BuildOperation.Upgrade, cast(Project)a.objectParam);
1027                     return true;
1028                 case IDEActions.RefreshProject:
1029                     refreshWorkspace();
1030                     return true;
1031                 case IDEActions.RevealProjectInExplorer:
1032                     revealProjectInExplorer(cast(Project)a.objectParam);
1033                     return true;
1034                 case IDEActions.WindowCloseDocument:
1035                     onTabClose(_tabs.selectedTabId);
1036                     return true;
1037                 case IDEActions.WindowCloseAllDocuments:
1038                     askForUnsavedEdits(delegate() {
1039                         closeAllDocuments();
1040                     });
1041                     return true;
1042                 case IDEActions.WindowShowHomeScreen:
1043                     showHomeScreen();
1044                     return true;
1045                 case IDEActions.FileOpenWorkspace:
1046                     // Already specified workspace
1047                     if (!a.stringParam.empty) {
1048                         openFileOrWorkspace(a.stringParam);
1049                         return true;
1050                     }
1051                     // Ask user for workspace to open
1052                     UIString caption = UIString.fromId("HEADER_OPEN_WORKSPACE_OR_PROJECT"c);
1053                     FileDialog dlg = createFileDialog(caption);
1054                     dlg.addFilter(FileFilterEntry(UIString.fromId("WORKSPACE_AND_PROJECT_FILES"c), "*.dlangidews;dub.json;dub.sdl;package.json"));
1055                     dlg.path = _settings.getRecentPath("FILE_OPEN_WORKSPACE_PATH");
1056                     dlg.dialogResult = delegate(Dialog d, const Action result) {
1057                         if (result.id == ACTION_OPEN.id) {
1058                             string filename = result.stringParam;
1059                             if (filename.length) {
1060                                 openFileOrWorkspace(filename);
1061                                 _settings.setRecentPath(dlg.path, "FILE_OPEN_WORKSPACE_PATH");
1062                             }
1063                         }
1064                     };
1065                     dlg.show();
1066                     return true;
1067                 case IDEActions.GoToDefinition:
1068                     if (currentEditor) {
1069                         Log.d("Trying to go to definition.");
1070                         currentEditor.editorTool.goToDefinition(currentEditor(), currentEditor.caretPos);
1071                     }
1072                     return true;
1073                 case IDEActions.GetDocComments:
1074                     Log.d("Trying to get doc comments.");
1075                     currentEditor.editorTool.getDocComments(currentEditor, currentEditor.caretPos, delegate(string[] results) {
1076                         if (results.length)
1077                             currentEditor.showDocCommentsPopup(results);
1078                     });
1079                     return true;
1080                 case IDEActions.GetParenCompletion:
1081                     Log.d("Trying to get paren completion.");
1082                     //auto results = currentEditor.editorTool.getParenCompletion(currentEditor, currentEditor.caretPos);
1083                     return true;
1084                 case IDEActions.GetCompletionSuggestions:
1085                     Log.d("Getting auto completion suggestions.");
1086                     currentEditor.editorTool.getCompletions(currentEditor, currentEditor.caretPos, delegate(dstring[] results, string[] icons) {
1087                         if (currentEditor)
1088                             currentEditor.showCompletionPopup(results, icons);
1089                     });
1090                     return true;
1091                 case IDEActions.EditPreferences:
1092                     showPreferences();
1093                     return true;
1094                 case IDEActions.ProjectSettings:
1095                     showProjectSettings(cast(Project)a.objectParam);
1096                     return true;
1097                 case IDEActions.SetStartupProject:
1098                     setStartupProject(cast(Project)a.objectParam);
1099                     return true;
1100                 case IDEActions.FindInFiles:
1101                     Log.d("Opening Search In Files panel");
1102                     if (!currentWorkspace) {
1103                         Log.d("No workspace is opened");
1104                         return true;
1105                     }
1106                     import dlangide.ui.searchPanel;
1107                     int searchPanelIndex = _logPanel.getTabs.tabIndex("search");
1108                     SearchWidget searchPanel = null;
1109                     if(searchPanelIndex == -1) {
1110                         searchPanel = new SearchWidget("search", this);
1111                         _logPanel.getTabs.addTab( searchPanel, "Search"d, null, true);
1112                     }
1113                     else {
1114                         searchPanel = cast(SearchWidget) _logPanel.getTabs.tabBody(searchPanelIndex);
1115                     }
1116                     _logPanel.getTabs.selectTab("search");
1117                     if(searchPanel !is null) { 
1118                         searchPanel.focus();
1119                         dstring selectedText;
1120                         if (currentEditor)
1121                             selectedText = currentEditor.getSelectedText();
1122                         searchPanel.setSearchText(selectedText);
1123                         searchPanel.checkSearchMode();
1124                     }
1125                     return true;
1126                 case IDEActions.FileNewWorkspace:
1127                     createNewProject(true);
1128                     return true;
1129                 case IDEActions.FileNewProject:
1130                     createNewProject(false);
1131                     return true;
1132                 case IDEActions.FileNew:
1133                     addProjectItem(cast(Object)a.objectParam);
1134                     return true;
1135                 case IDEActions.ProjectFolderRemoveItem:
1136                     removeProjectItem(a.objectParam);
1137                     return true;
1138                 case IDEActions.ProjectFolderRefresh:
1139                     refreshProjectItem(a.objectParam);
1140                     return true;
1141                 case IDEActions.CloseWorkspace:
1142                     closeWorkspace();
1143                     return true;
1144                 default:
1145                     return super.handleAction(a);
1146             }
1147         }
1148         return false;
1149     }
1150 
1151     @property ProjectSourceFile currentEditorSourceFile() {
1152         TabItem tab = _tabs.selectedTab;
1153         if (tab) {
1154             return cast(ProjectSourceFile)tab.objectParam;
1155         }
1156         return null;
1157     }
1158 
1159     void closeWorkspace() {
1160         if (currentWorkspace) {
1161             saveListOfOpenedFiles();
1162             currentWorkspace.save();
1163         }
1164         askForUnsavedEdits(delegate() {
1165             setWorkspace(null);
1166             showHomeScreen();
1167         });
1168     }
1169 
1170     void onBreakpointListChanged(ProjectSourceFile sourcefile, Breakpoint[] breakpoints) {
1171         if (!currentWorkspace)
1172             return;
1173         if (sourcefile) {
1174             currentWorkspace.setSourceFileBreakpoints(sourcefile, breakpoints);
1175         }
1176         if (_debugHandler)
1177             _debugHandler.onBreakpointListUpdated(currentWorkspace.getBreakpoints());
1178     }
1179 
1180     void onBookmarkListChanged(ProjectSourceFile sourcefile, EditorBookmark[] bookmarks) {
1181         if (!currentWorkspace)
1182             return;
1183         if (sourcefile)
1184             currentWorkspace.setSourceFileBookmarks(sourcefile, bookmarks);
1185     }
1186 
1187     void refreshProjectItem(const Object obj) {
1188         if (currentWorkspace is null)
1189             return;
1190         Project project;
1191         ProjectFolder folder;
1192         if (cast(Workspace)obj) {
1193             Workspace ws = cast(Workspace)obj;
1194             ws.refresh();
1195             refreshWorkspace();
1196         } else if (cast(Project)obj) {
1197             project = cast(Project)obj;
1198         } else if (cast(ProjectFolder)obj) {
1199             folder = cast(ProjectFolder)obj;
1200             project = folder.project;
1201         } else if (cast(ProjectSourceFile)obj) {
1202             ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1203             folder = cast(ProjectFolder)srcfile.parent;
1204             project = srcfile.project;
1205         } else {
1206             ProjectSourceFile srcfile = currentEditorSourceFile;
1207             if (srcfile) {
1208                 folder = cast(ProjectFolder)srcfile.parent;
1209                 project = srcfile.project;
1210             }
1211         }
1212         if (project) {
1213             project.refresh();
1214             refreshWorkspace();
1215         }
1216     }
1217 
1218     void removeProjectItem(const Object obj) {
1219         if (currentWorkspace is null)
1220             return;
1221         ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1222         if (!srcfile)
1223             return;
1224         Project project = srcfile.project;
1225         if (!project)
1226             return;
1227         window.showMessageBox(UIString.fromRaw("Remove file"d), 
1228                 UIString.fromRaw("Do you want to remove file "d ~ srcfile.name ~ "?"), 
1229                 [ACTION_YES, ACTION_NO], 
1230                 1, delegate(const Action result) {
1231                     if (result == StandardAction.Yes) {
1232                         // save and close
1233                         try {
1234                             import std.file : remove;
1235                             closeTab(srcfile.filename);
1236                             remove(srcfile.filename);
1237                             project.refresh();
1238                             refreshWorkspace();
1239                         } catch (Exception e) {
1240                             Log.e("Error while removing file");
1241                         }
1242                     }
1243                     // else ignore
1244                     return true;
1245                 });
1246 
1247     }
1248 
1249     /// add new file to project
1250     void addProjectItem(Object obj) {
1251         if (currentWorkspace is null)
1252             return;
1253         if (obj is null && _wsPanel !is null && !currentEditorSourceFile) {
1254             obj = _wsPanel.selectedProjectItem;
1255             if (!obj)
1256                 obj = currentWorkspace.startupProject;
1257         }
1258         Project project;
1259         ProjectFolder folder;
1260         if (cast(Project)obj) {
1261             project = cast(Project)obj;
1262             folder = project.firstSourceFolder;
1263         } else if (cast(ProjectFolder)obj) {
1264             folder = cast(ProjectFolder)obj;
1265             project = folder.project;
1266         } else if (cast(ProjectSourceFile)obj) {
1267             ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
1268             folder = cast(ProjectFolder)srcfile.parent;
1269             project = srcfile.project;
1270         } else {
1271             ProjectSourceFile srcfile = currentEditorSourceFile;
1272             if (srcfile) {
1273                 folder = cast(ProjectFolder)srcfile.parent;
1274                 project = srcfile.project;
1275             }
1276         }
1277         if (project && folder && project.workspace is currentWorkspace) {
1278             NewFileDlg dlg = new NewFileDlg(this, project, folder);
1279             dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1280                 if (result.id == ACTION_FILE_NEW_SOURCE_FILE.id) {
1281                     FileCreationResult res = cast(FileCreationResult)result.objectParam;
1282                     if (res) {
1283                         //res.project.reload();
1284                         res.project.refresh();
1285                         refreshWorkspace();
1286                         if (isSupportedSourceTextFileFormat(res.filename)) {
1287                             openSourceFile(res.filename, null, true);
1288                         }
1289                     }
1290                 }
1291             };
1292             dlg.show();
1293         }
1294     }
1295 
1296     void createNewProject(bool newWorkspace) {
1297         if (currentWorkspace is null)
1298             newWorkspace = true;
1299         string location = _settings.getRecentPath("FILE_OPEN_WORKSPACE_PATH");
1300         if (newWorkspace && location)
1301             location = location.dirName;
1302         NewProjectDlg dlg = new NewProjectDlg(this, newWorkspace, currentWorkspace, location);
1303         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1304             if (result.id == ACTION_FILE_NEW_PROJECT.id || result.id == ACTION_FILE_NEW_WORKSPACE.id) {
1305                 //Log.d("settings after edit:\n", s.toJSON(true));
1306                 ProjectCreationResult res = cast(ProjectCreationResult)result.objectParam;
1307                 if (res) {
1308                     // open workspace/project
1309                     if (currentWorkspace is null || res.workspace !is currentWorkspace) {
1310                         // open new workspace
1311                         setWorkspace(res.workspace);
1312                         refreshWorkspace();
1313                         hideHomeScreen();
1314                     } else {
1315                         // project added to current workspace
1316                         loadProject(res.project);
1317                         refreshWorkspace();
1318                         hideHomeScreen();
1319                     }
1320                 }
1321             }
1322         };
1323         dlg.show();
1324     }
1325 
1326     void showPreferences() {
1327         //Log.d("settings before copy:\n", _settings.setting.toJSON(true));
1328         Setting s = _settings.copySettings();
1329         //Log.d("settings after copy:\n", s.toJSON(true));
1330         SettingsDialog dlg = new SettingsDialog(UIString.fromId("HEADER_SETTINGS"c), window, s, createSettingsPages());
1331         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1332             if (result.id == ACTION_APPLY.id) {
1333                 //Log.d("settings after edit:\n", s.toJSON(true));
1334                 _settings.applySettings(s);
1335                 applySettings(_settings);
1336                 _settings.save();
1337             }
1338         };
1339         dlg.show();
1340     }
1341 
1342     void setStartupProject(Project project) {
1343         if (!currentWorkspace)
1344             return;
1345         if (!project)
1346             return;
1347         currentWorkspace.startupProject = project;
1348         warmUpImportPaths(project);
1349         if (_wsPanel)
1350             _wsPanel.updateDefault();
1351     }
1352 
1353     void showProjectSettings(Project project) {
1354         if (!currentWorkspace)
1355             return;
1356         if (!project)
1357             project = currentWorkspace.startupProject;
1358         if (!project)
1359             return;
1360         Setting s = project.settings.copySettings();
1361         SettingsDialog dlg = new SettingsDialog(UIString.fromRaw(project.name ~ " settings"d), window, s, createProjectSettingsPages());
1362         dlg.dialogResult = delegate(Dialog dlg, const Action result) {
1363             if (result.id == ACTION_APPLY.id) {
1364                 //Log.d("settings after edit:\n", s.toJSON(true));
1365                 project.settings.applySettings(s);
1366                 project.settings.save();
1367             }
1368         };
1369         dlg.show();
1370     }
1371 
1372     // Applying settings to tabs/sources and it's opening
1373     void applySettings(IDESettings settings) {
1374         for (int i = _tabs.tabCount - 1; i >= 0; i--) {
1375             DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i);
1376             if (ed) {
1377                 applySettings(ed, settings);
1378             }
1379         }
1380         FontManager.fontGamma = settings.fontGamma;
1381         FontManager.hintingMode = settings.hintingMode;
1382         FontManager.minAnitialiasedFontSize = settings.minAntialiasedFontSize;
1383         Platform.instance.uiLanguage = settings.uiLanguage;
1384         Platform.instance.uiTheme = settings.uiTheme;
1385         bool needUpdateTheme = false;
1386         string oldFontFace = currentTheme.fontFace;
1387         string newFontFace = settings.uiFontFace;
1388         if (newFontFace == "Default")
1389             newFontFace = "Helvetica Neue,Verdana,Arial,DejaVu Sans,Liberation Sans,Helvetica,Roboto,Droid Sans";
1390         int oldFontSize = currentTheme.fontSize;
1391         if (oldFontFace != newFontFace) {
1392             currentTheme.fontFace = newFontFace;
1393             needUpdateTheme = true;
1394         }
1395         if (oldFontSize != settings.uiFontSize) {
1396             currentTheme.fontSize = settings.uiFontSize;
1397             needUpdateTheme = true;
1398         }
1399         if (needUpdateTheme) {
1400             Log.d("updating theme after UI font change");
1401             Platform.instance.onThemeChanged();
1402         }
1403         requestLayout();
1404     }
1405 
1406     void applySettings(DSourceEdit editor, IDESettings settings) {
1407         editor.settings(settings).applySettings();
1408     }
1409 
1410     private bool loadProject(Project project) {
1411         if (!project.load()) {
1412             _logPanel.logLine("Cannot read project " ~ project.filename);
1413             window.showMessageBox(UIString.fromId("ERROR_OPEN_PROJECT"c).value, UIString.fromId("ERROR_OPENING_PROJECT"c).value ~ toUTF32(project.filename));
1414             return false;
1415         }
1416         const auto msg = UIString.fromId("MSG_OPENED_PROJECT"c);
1417         _logPanel.logLine(toUTF32("Project file " ~ project.filename ~  " is opened ok"));
1418         
1419         warmUpImportPaths(project);
1420         return true;
1421     }
1422 
1423     public void warmUpImportPaths(Project project) {
1424         dcdInterface.warmUp(project.importPaths);
1425     }
1426 
1427     void restoreListOfOpenedFiles() {
1428         // All was opened, attempt to restore files
1429         WorkspaceFile[] files = currentWorkspace.files();
1430         for (int i; i < files.length; i++) 
1431             with (files[i])
1432             {
1433                 // Opening file
1434                 if (openSourceFile(filename))
1435                 {
1436                     auto index = _tabs.tabIndex(filename);
1437                     // file is opened in tab
1438                     auto source = cast(DSourceEdit)_tabs.tabBody(filename);
1439                     // Caret position
1440                     source.setCaretPos(column, row, true, true);
1441                 }
1442             }
1443     }
1444 
1445     void saveListOfOpenedFiles() {
1446         WorkspaceFile[] files;
1447         for (auto i = 0; i < _tabs.tabCount(); i++)
1448         {
1449             auto edit = cast(DSourceEdit)_tabs.tabBody(i);
1450             if (edit !is null) {
1451                 auto file = new WorkspaceFile();
1452                 file.filename = edit.filename();
1453                 file.row = edit.caretPos.pos;
1454                 file.column = edit.caretPos.line;
1455                 files ~= file;
1456             }
1457         }
1458         currentWorkspace.files(files);
1459         // saving workspace
1460         currentWorkspace.save();
1461     }
1462 
1463     void openFileOrWorkspace(string filename) {
1464         // Open DlangIDE workspace file
1465         if (filename.isWorkspaceFile) {
1466             Workspace ws = new Workspace(this);
1467             if (ws.load(filename)) {
1468                 askForUnsavedEdits(delegate() {
1469                     setWorkspace(ws);
1470                     hideHomeScreen();
1471                     // Write workspace to recent workspaces list
1472                     _settings.updateRecentWorkspace(filename);
1473                     restoreListOfOpenedFiles();
1474                 });
1475             } else {
1476                 window.showMessageBox(UIString.fromId("ERROR_OPEN_WORKSPACE"c).value, UIString.fromId("ERROR_OPENING_WORKSPACE"c).value);
1477                 return;
1478             }
1479         } else if (filename.isProjectFile) { // Open non-DlangIDE project file or DlangIDE project
1480             _logPanel.clear();
1481             const auto msg = UIString.fromId("MSG_TRY_OPEN_PROJECT"c).value;
1482             _logPanel.logLine(msg ~ toUTF32(" " ~ filename));
1483             Project project = new Project(currentWorkspace, filename);
1484             if (!loadProject(project)) {
1485                 //window.showMessageBox(UIString.fromId("MSG_OPEN_PROJECT"c), UIString.fromId("ERROR_INVALID_WS_OR_PROJECT_FILE"c));
1486                 //_logPanel.logLine("File is not recognized as DlangIDE project or workspace file");
1487                 return;
1488             }
1489             string defWsFile = project.defWorkspaceFile;
1490             if (currentWorkspace) {
1491                 Project existing = currentWorkspace.findProject(project.filename);
1492                 if (existing) {
1493                     _logPanel.logLine("Project is already in workspace"d);
1494                     window.showMessageBox(UIString.fromId("MSG_OPEN_PROJECT"c), UIString.fromId("MSG_PROJECT_ALREADY_OPENED"c));
1495                     return;
1496                 }
1497                 window.showMessageBox(UIString.fromId("MSG_OPEN_PROJECT"c), UIString.fromId("QUESTION_NEW_WORKSPACE"c),
1498 
1499                                       [ACTION_ADD_TO_CURRENT_WORKSPACE, ACTION_CREATE_NEW_WORKSPACE, ACTION_CANCEL], 0, delegate(const Action result) {
1500                                           if (result.id == IDEActions.CreateNewWorkspace) {
1501                                               // new ws
1502                                               createNewWorkspaceForExistingProject(project);
1503                                               hideHomeScreen();
1504                                           } else if (result.id == IDEActions.AddToCurrentWorkspace) {
1505                                               // add to current
1506                                               currentWorkspace.addProject(project);
1507                                               loadProject(project);
1508                                               currentWorkspace.save();
1509                                               refreshWorkspace();
1510                                               hideHomeScreen();
1511                                           }
1512                                           return true;
1513                                       });
1514             } else {
1515                 // new workspace file
1516                 createNewWorkspaceForExistingProject(project);
1517             }
1518         } else {
1519             _logPanel.logLine("File is not recognized as DlangIDE project or workspace file");
1520             window.showMessageBox(UIString.fromId("ERROR_INVALID_WORKSPACE_FILE"c), UIString.fromId("ERROR_INVALID_WS_OR_PROJECT_FILE"c));
1521         }
1522     }
1523 
1524     void refreshWorkspace() {
1525         _logPanel.logLine("Refreshing workspace");
1526         _wsPanel.reloadItems();
1527         closeRemovedDocuments();
1528     }
1529 
1530     void createNewWorkspaceForExistingProject(Project project) {
1531         string defWsFile = project.defWorkspaceFile;
1532         _logPanel.logLine("Creating new workspace " ~ defWsFile);
1533         // new ws
1534         Workspace ws = new Workspace(this);
1535         ws.name = project.name;
1536         ws.description = project.description;
1537         Log.d("workspace name: ", project.name);
1538         Log.d("workspace description: ", project.description);
1539         ws.addProject(project);
1540         // Load project data
1541         loadProject(project);
1542         ws.save(defWsFile);
1543         setWorkspace(ws);
1544         _logPanel.logLine("Done");
1545     }
1546 
1547     //bool loadWorkspace(string path) {
1548     //    // testing workspace loader
1549     //    Workspace ws = new Workspace();
1550     //    ws.load(path);
1551     //    setWorkspace(ws);
1552     //    //ws.save(ws.filename ~ ".bak");
1553     //    return true;
1554     //}
1555 
1556     void setWorkspace(Workspace ws) {
1557         closeAllDocuments();
1558         currentWorkspace = ws;
1559         _wsPanel.workspace = ws;
1560         requestActionsUpdate();
1561         // Open main file for project
1562         if (ws && ws.startupProject && ws.startupProject.mainSourceFile 
1563             && (currentWorkspace.files == null || currentWorkspace.files.length == 0)) {
1564             openSourceFile(ws.startupProject.mainSourceFile.filename);
1565             _tabs.setFocus();
1566         }
1567         if (ws) {
1568             _wsPanel.visibility = Visibility.Visible;
1569             _settings.updateRecentWorkspace(ws.filename);
1570             _settings.setRecentPath(ws.dir, "FILE_OPEN_WORKSPACE_PATH");
1571             if (ws.startupProject) {
1572                 warmUpImportPaths(ws.startupProject);
1573             }
1574         } else {
1575             _wsPanel.visibility = Visibility.Gone;
1576         }
1577 
1578     }
1579 
1580     void refreshProject(Project project) {
1581         if (currentWorkspace && project.loadSelections()) {
1582             currentWorkspace.cleanupUnusedDependencies();
1583             refreshWorkspace();
1584         }
1585     }
1586 
1587     void revealProjectInExplorer(Project project) {
1588         Platform.instance.showInFileManager(project.items.filename);
1589     }
1590 
1591     void buildProject(BuildOperation buildOp, Project project, BuildResultListener listener = null) {
1592         if (!currentWorkspace) {
1593             _logPanel.logLine("No workspace is opened");
1594             return;
1595         }
1596         if (!project)
1597             project = currentWorkspace.startupProject;
1598         if (!project) {
1599             _logPanel.logLine("No project is opened");
1600             return;
1601         }
1602         _logPanel.activateLogTab();
1603         if (!listener) {
1604             if (buildOp == BuildOperation.Upgrade || buildOp == BuildOperation.Build || buildOp == BuildOperation.Rebuild) {
1605                 listener = delegate(int result) {
1606                     if (!result) {
1607                         // success: update workspace
1608                         refreshProject(project);
1609                     } else {
1610                         handleBuildError(result, project);
1611                     }
1612                 };
1613             }
1614         }
1615         ProjectSettings projectSettings = project.settings;
1616         string toolchain = projectSettings.getToolchain(_settings);
1617         string arch = projectSettings.getArch(_settings);
1618         string dubExecutable = _settings.dubExecutable;
1619         string dubAdditionalParams = projectSettings.getDubAdditionalParams(_settings);
1620         Builder op = new Builder(this, project, _logPanel, currentWorkspace.projectConfiguration, currentWorkspace.buildConfiguration, buildOp, 
1621                                  dubExecutable, dubAdditionalParams,
1622                                  toolchain,
1623                                  arch,
1624                                  listener);
1625         setBackgroundOperation(op);
1626     }
1627     
1628     /// updates list of available configurations
1629     void setProjectConfigurations(dstring[] items) {
1630         projectConfigurationCombo.items = items;
1631     }
1632     
1633     /// handle files dropped to application window
1634     void onFilesDropped(string[] filenames) {
1635         //Log.d("onFilesDropped(", filenames, ")");
1636         bool first = true;
1637         for (int i = 0; i < filenames.length; i++) {
1638             openSourceFile(filenames[i], null, first);
1639             first = false;
1640         }
1641     }
1642 
1643     /// return false to prevent closing
1644     bool onCanClose() {
1645         askForUnsavedEdits(delegate() {
1646             if (currentWorkspace) {
1647                 // Remember opened files
1648                 saveListOfOpenedFiles();
1649             }
1650             window.close();
1651         });
1652         return false;
1653     }
1654     /// called when main window is closing
1655     void onWindowClose() {
1656         Log.i("onWindowClose()");
1657         stopExecution();
1658     }
1659 }
1660