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