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