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