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