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