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