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