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.tools.d.dcdserver; 29 import dlangide.workspace.workspace; 30 import dlangide.workspace.project; 31 import dlangide.builders.builder; 32 import dlangide.tools.editorTool; 33 34 import ddebug.common.execution; 35 import ddebug.common.nodebug; 36 37 import std.conv; 38 import std.utf; 39 import std.algorithm; 40 import std.path; 41 42 bool isSupportedSourceTextFileFormat(string filename) { 43 return (filename.endsWith(".d") || filename.endsWith(".txt") || filename.endsWith(".cpp") || filename.endsWith(".h") || filename.endsWith(".c") 44 || filename.endsWith(".json") || filename.endsWith(".dd") || filename.endsWith(".ddoc") || filename.endsWith(".xml") || filename.endsWith(".html") 45 || filename.endsWith(".html") || filename.endsWith(".css") || filename.endsWith(".log") || filename.endsWith(".hpp")); 46 } 47 48 class BackgroundOperationWatcherTest : BackgroundOperationWatcher { 49 this(AppFrame frame) { 50 super(frame); 51 } 52 int _counter; 53 /// returns description of background operation to show in status line 54 override @property dstring description() { return "Test progress: "d ~ to!dstring(_counter); } 55 /// returns icon of background operation to show in status line 56 override @property string icon() { return "folder"; } 57 /// update background operation status 58 override void update() { 59 _counter++; 60 if (_counter >= 100) 61 _finished = true; 62 super.update(); 63 } 64 } 65 66 /// DIDE app frame 67 class IDEFrame : AppFrame, ProgramExecutionStatusListener { 68 69 private ToolBarComboBox projectConfigurationCombo; 70 71 MenuItem mainMenuItems; 72 WorkspacePanel _wsPanel; 73 OutputPanel _logPanel; 74 DockHost _dockHost; 75 TabWidget _tabs; 76 DCDServer _dcdServer; 77 IDESettings _settings; 78 ProgramExecution _execution; 79 80 dstring frameWindowCaptionSuffix = "DLangIDE"d; 81 82 this(Window window) { 83 super(); 84 window.mainWidget = this; 85 window.onFilesDropped = &onFilesDropped; 86 window.onCanClose = &onCanClose; 87 window.onClose = &onWindowClose; 88 applySettings(_settings); 89 } 90 91 /// stop current program execution 92 void stopExecution() { 93 if (_execution) { 94 _logPanel.logLine("Stopping program execution"); 95 Log.d("Stopping execution"); 96 _execution.stop(); 97 //destroy(_execution); 98 _execution = null; 99 } 100 } 101 102 /// returns true if program execution or debugging is active 103 @property bool isExecutionActive() { 104 return _execution !is null; 105 } 106 107 /// called when program execution is stopped 108 protected void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) { 109 executeInUiThread(delegate() { 110 Log.d("onProgramExecutionStatus process: ", process.executableFile, " status: ", status, " exitCode: ", exitCode); 111 _execution = null; 112 // TODO: update state 113 switch(status) { 114 case ExecutionStatus.Error: 115 _logPanel.logLine("Cannot run program " ~ process.executableFile); 116 break; 117 case ExecutionStatus.Finished: 118 _logPanel.logLine("Program " ~ process.executableFile ~ " finished with exit code " ~ to!string(exitCode)); 119 break; 120 case ExecutionStatus.Killed: 121 _logPanel.logLine("Program " ~ process.executableFile ~ " is killed"); 122 break; 123 default: 124 _logPanel.logLine("Program " ~ process.executableFile ~ " is finished"); 125 break; 126 } 127 _statusLine.setBackgroundOperationStatus(null, null); 128 }); 129 } 130 131 protected void buildAndRunProject(Project project) { 132 if (!currentWorkspace) 133 return; 134 if (!project) 135 project = currentWorkspace.startupProject; 136 if (!project) { 137 window.showMessageBox(UIString("Cannot run project"d), UIString("Startup project is not specified"d)); 138 return; 139 } 140 buildProject(BuildOperation.Build, project, delegate(int result) { 141 if (!result) { 142 runProject(); 143 } 144 }); 145 } 146 147 protected void runProject() { 148 import std.file; 149 stopExecution(); 150 if (!currentWorkspace) 151 return; 152 Project project = currentWorkspace.startupProject; 153 if (!project) { 154 window.showMessageBox(UIString("Cannot run project"d), UIString("Startup project is not specified"d)); 155 return; 156 } 157 // build project 158 // TODO 159 string executableFileName = project.executableFileName; 160 if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) { 161 window.showMessageBox(UIString("Cannot run project"d), UIString("Cannot find executable"d)); 162 return; 163 } 164 string[] args; 165 string externalConsoleExecutable = null; 166 string workingDirectory = project.workingDirectory; 167 if (project.runInExternalConsole) { 168 version(Windows) { 169 } else { 170 externalConsoleExecutable = _settings.terminalExecutable; 171 } 172 } 173 // TODO: provide thread safe listener 174 _logPanel.logLine("Starting " ~ executableFileName); 175 _statusLine.setBackgroundOperationStatus("debug-run", "running..."d); 176 _execution = new ProgramExecutionNoDebug(executableFileName, args, workingDirectory, externalConsoleExecutable, this); 177 _execution.run(); 178 } 179 180 override protected void init() { 181 _appName = "dlangide"; 182 //_editorTool = new DEditorTool(this); 183 _dcdServer = new DCDServer(); 184 _settings = new IDESettings(buildNormalizedPath(settingsDir, "settings.json")); 185 _settings.load(); 186 _settings.updateDefaults(); 187 _settings.save(); 188 super.init(); 189 } 190 191 /// move focus to editor in currently selected tab 192 void focusEditor(string id) { 193 Widget w = _tabs.tabBody(id); 194 if (w) { 195 if (w.visible) 196 w.setFocus(); 197 } 198 } 199 200 /// source file selected in workspace tree 201 bool onSourceFileSelected(ProjectSourceFile file, bool activate) { 202 Log.d("onSourceFileSelected ", file.filename); 203 return openSourceFile(file.filename, file, activate); 204 } 205 206 /// returns global IDE settings 207 @property IDESettings settings() { return _settings; } 208 209 /// 210 bool onCompilerLogIssueClick(dstring filename, int line, int column) 211 { 212 Log.d("onCompilerLogIssueClick ", filename); 213 214 import std.conv:to; 215 openSourceFile(to!string(filename)); 216 217 currentEditor().setCaretPos(line-1,column); 218 219 return true; 220 } 221 222 void onModifiedStateChange(Widget source, bool modified) { 223 // 224 Log.d("onModifiedStateChange ", source.id, " modified=", modified); 225 int index = _tabs.tabIndex(source.id); 226 if (index >= 0) { 227 dstring name = toUTF32((modified ? "* " : "") ~ baseName(source.id)); 228 _tabs.renameTab(index, name); 229 } 230 } 231 232 bool openSourceFile(string filename, ProjectSourceFile file = null, bool activate = true) { 233 if (!file && !filename) 234 return false; 235 if (!file) 236 file = _wsPanel.findSourceFileItem(filename, false); 237 238 //if(!file) 239 // return false; 240 241 if (file) 242 filename = file.filename; 243 244 Log.d("openSourceFile ", filename); 245 int index = _tabs.tabIndex(filename); 246 if (index >= 0) { 247 // file is already opened in tab 248 _tabs.selectTab(index, true); 249 } else { 250 // open new file 251 DSourceEdit editor = new DSourceEdit(filename); 252 if (file ? editor.load(file) : editor.load(filename)) { 253 _tabs.addTab(editor, toUTF32(baseName(filename)), null, true); 254 index = _tabs.tabIndex(filename); 255 TabItem tab = _tabs.tab(filename); 256 tab.objectParam = file; 257 editor.modifiedStateChange = &onModifiedStateChange; 258 applySettings(editor, settings); 259 _tabs.selectTab(index, true); 260 if( filename.endsWith(".d") ) 261 editor.editorTool = new DEditorTool(this); 262 else 263 editor.editorTool = new DefaultEditorTool(this); 264 } else { 265 destroy(editor); 266 if (window) 267 window.showMessageBox(UIString("File open error"d), UIString("Failed to open file "d ~ toUTF32(file.filename))); 268 return false; 269 } 270 } 271 if (activate) { 272 focusEditor(filename); 273 } 274 requestLayout(); 275 return true; 276 } 277 278 static immutable HOME_SCREEN_ID = "HOME_SCREEN"; 279 void showHomeScreen() { 280 int index = _tabs.tabIndex(HOME_SCREEN_ID); 281 if (index >= 0) { 282 _tabs.selectTab(index, true); 283 } else { 284 HomeScreen home = new HomeScreen(HOME_SCREEN_ID, this); 285 _tabs.addTab(home, "Home"d, null, true); 286 _tabs.selectTab(HOME_SCREEN_ID, true); 287 } 288 } 289 290 void hideHomeScreen() { 291 _tabs.removeTab(HOME_SCREEN_ID); 292 } 293 294 void onTabChanged(string newActiveTabId, string previousTabId) { 295 int index = _tabs.tabIndex(newActiveTabId); 296 if (index >= 0) { 297 TabItem tab = _tabs.tab(index); 298 ProjectSourceFile file = cast(ProjectSourceFile)tab.objectParam; 299 if (file) { 300 //setCurrentProject(file.project); 301 // tab is source file editor 302 _wsPanel.selectItem(file); 303 focusEditor(file.filename); 304 } 305 window.windowCaption(tab.text.value ~ " - "d ~ frameWindowCaptionSuffix); 306 } 307 } 308 309 // returns DSourceEdit from currently active tab (if it's editor), null if current tab is not editor or no tabs open 310 DSourceEdit currentEditor() { 311 return cast(DSourceEdit)_tabs.selectedTabBody(); 312 } 313 314 /// close tab w/o confirmation 315 void closeTab(string tabId) { 316 _wsPanel.selectItem(null); 317 _tabs.removeTab(tabId); 318 } 319 320 /// close all editor tabs 321 void closeAllDocuments() { 322 for (int i = _tabs.tabCount - 1; i >= 0; i--) { 323 DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i); 324 if (ed) { 325 closeTab(ed.id); 326 } 327 } 328 } 329 330 /// close editor tabs for which files are removed from filesystem 331 void closeRemovedDocuments() { 332 import std.file; 333 for (int i = _tabs.tabCount - 1; i >= 0; i--) { 334 DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i); 335 if (ed) { 336 if (!exists(ed.id) || !isFile(ed.id)) { 337 closeTab(ed.id); 338 } 339 } 340 } 341 } 342 343 /// returns first unsaved document 344 protected DSourceEdit hasUnsavedEdits() { 345 for (int i = _tabs.tabCount - 1; i >= 0; i--) { 346 DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i); 347 if (ed && ed.content.modified) { 348 return ed; 349 } 350 } 351 return null; 352 } 353 354 protected void askForUnsavedEdits(void delegate() onConfirm) { 355 DSourceEdit ed = hasUnsavedEdits(); 356 if (!ed) { 357 // no unsaved edits 358 onConfirm(); 359 return; 360 } 361 string tabId = ed.id; 362 // tab content is modified - ask for confirmation 363 window.showMessageBox(UIString("Close file "d ~ toUTF32(baseName(tabId))), UIString("Content of this file has been changed."d), 364 [ACTION_SAVE, ACTION_SAVE_ALL, ACTION_DISCARD_CHANGES, ACTION_DISCARD_ALL, ACTION_CANCEL], 365 0, delegate(const Action result) { 366 if (result == StandardAction.Save) { 367 // save and close 368 ed.save(); 369 askForUnsavedEdits(onConfirm); 370 } else if (result == StandardAction.DiscardChanges) { 371 // close, don't save 372 closeTab(tabId); 373 closeAllDocuments(); 374 onConfirm(); 375 } else if (result == StandardAction.SaveAll) { 376 ed.save(); 377 for(;;) { 378 DSourceEdit editor = hasUnsavedEdits(); 379 if (!editor) 380 break; 381 editor.save(); 382 } 383 closeAllDocuments(); 384 onConfirm(); 385 } else if (result == StandardAction.DiscardAll) { 386 // close, don't save 387 closeAllDocuments(); 388 onConfirm(); 389 } 390 // else ignore 391 return true; 392 }); 393 } 394 395 protected void onTabClose(string tabId) { 396 Log.d("onTabClose ", tabId); 397 int index = _tabs.tabIndex(tabId); 398 if (index >= 0) { 399 DSourceEdit d = cast(DSourceEdit)_tabs.tabBody(tabId); 400 if (d && d.content.modified) { 401 // tab content is modified - ask for confirmation 402 window.showMessageBox(UIString("Close tab"d), UIString("Content of "d ~ toUTF32(baseName(tabId)) ~ " file has been changed."d), 403 [ACTION_SAVE, ACTION_DISCARD_CHANGES, ACTION_CANCEL], 404 0, delegate(const Action result) { 405 if (result == StandardAction.Save) { 406 // save and close 407 d.save(); 408 closeTab(tabId); 409 } else if (result == StandardAction.DiscardChanges) { 410 // close, don't save 411 closeTab(tabId); 412 } 413 // else ignore 414 return true; 415 }); 416 } else { 417 closeTab(tabId); 418 } 419 } 420 } 421 422 /// create app body widget 423 override protected Widget createBody() { 424 _dockHost = new DockHost(); 425 426 //============================================================= 427 // Create body - Tabs 428 429 // editor tabs 430 _tabs = new TabWidget("TABS"); 431 _tabs.hiddenTabsVisibility = Visibility.Gone; 432 _tabs.setStyles(STYLE_DOCK_HOST_BODY, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT); 433 _tabs.tabChanged = &onTabChanged; 434 _tabs.tabClose = &onTabClose; 435 436 _dockHost.bodyWidget = _tabs; 437 438 //============================================================= 439 // Create workspace docked panel 440 _wsPanel = new WorkspacePanel("workspace"); 441 _wsPanel.sourceFileSelectionListener = &onSourceFileSelected; 442 _wsPanel.workspaceActionListener = &handleAction; 443 _wsPanel.dockAlignment = DockAlignment.Left; 444 _dockHost.addDockedWindow(_wsPanel); 445 446 _logPanel = new OutputPanel("output"); 447 _logPanel.compilerLogIssueClickHandler = &onCompilerLogIssueClick; 448 _logPanel.appendText(null, "DlangIDE is started\nHINT: Try to open some DUB project\n"d); 449 string dubPath = findExecutablePath("dub"); 450 string dmdPath = findExecutablePath("dmd"); 451 string ldcPath = findExecutablePath("ldc2"); 452 string gdcPath = findExecutablePath("gdc"); 453 _logPanel.appendText(null, dubPath ? ("dub path: "d ~ toUTF32(dubPath) ~ "\n"d) : ("dub is not found! cannot build projects without DUB\n"d)); 454 _logPanel.appendText(null, dmdPath ? ("dmd path: "d ~ toUTF32(dmdPath) ~ "\n"d) : ("dmd compiler is not found!\n"d)); 455 _logPanel.appendText(null, ldcPath ? ("ldc path: "d ~ toUTF32(ldcPath) ~ "\n"d) : ("ldc compiler is not found!\n"d)); 456 _logPanel.appendText(null, gdcPath ? ("gdc path: "d ~ toUTF32(gdcPath) ~ "\n"d) : ("gdc compiler is not found!\n"d)); 457 458 if (_dcdServer.start()) { 459 _logPanel.appendText(null, "dcd-server is started on port "d ~ to!dstring(_dcdServer.port) ~ "\n"d); 460 } else { 461 _logPanel.appendText(null, "cannot start dcd-server: code completion for D code will not work"d); 462 } 463 464 _dockHost.addDockedWindow(_logPanel); 465 466 return _dockHost; 467 } 468 469 /// create main menu 470 override protected MainMenu createMainMenu() { 471 472 mainMenuItems = new MenuItem(); 473 MenuItem fileItem = new MenuItem(new Action(1, "MENU_FILE")); 474 MenuItem fileNewItem = new MenuItem(new Action(1, "MENU_FILE_NEW")); 475 fileNewItem.add(ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT); 476 fileItem.add(fileNewItem); 477 fileItem.add(ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 478 ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_WORKSPACE_CLOSE, ACTION_FILE_EXIT); 479 480 MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT")); 481 editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, 482 ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_FIND_TEXT); 483 MenuItem editItemAdvanced = new MenuItem(new Action(221, "MENU_EDIT_ADVANCED")); 484 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); 485 editItem.add(editItemAdvanced); 486 487 editItem.add(ACTION_EDIT_PREFERENCES); 488 489 MenuItem navItem = new MenuItem(new Action(21, "MENU_NAVIGATE")); 490 navItem.add(ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS); 491 492 MenuItem projectItem = new MenuItem(new Action(21, "MENU_PROJECT")); 493 projectItem.add(ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, ACTION_PROJECT_SETTINGS); 494 495 MenuItem buildItem = new MenuItem(new Action(22, "MENU_BUILD")); 496 buildItem.add(ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN, 497 ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN); 498 499 MenuItem debugItem = new MenuItem(new Action(23, "MENU_DEBUG")); 500 debugItem.add(ACTION_DEBUG_START, ACTION_DEBUG_START_NO_DEBUG, 501 ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE); 502 503 504 MenuItem windowItem = new MenuItem(new Action(3, "MENU_WINDOW"c)); 505 windowItem.add(new Action(30, "MENU_WINDOW_PREFERENCES")); 506 windowItem.add(ACTION_WINDOW_CLOSE_ALL_DOCUMENTS); 507 MenuItem helpItem = new MenuItem(new Action(4, "MENU_HELP"c)); 508 helpItem.add(new Action(40, "MENU_HELP_VIEW_HELP")); 509 helpItem.add(ACTION_HELP_ABOUT); 510 mainMenuItems.add(fileItem); 511 mainMenuItems.add(editItem); 512 mainMenuItems.add(projectItem); 513 mainMenuItems.add(navItem); 514 mainMenuItems.add(buildItem); 515 mainMenuItems.add(debugItem); 516 //mainMenuItems.add(viewItem); 517 mainMenuItems.add(windowItem); 518 mainMenuItems.add(helpItem); 519 520 MainMenu mainMenu = new MainMenu(mainMenuItems); 521 //mainMenu.backgroundColor = 0xd6dbe9; 522 return mainMenu; 523 } 524 525 /// override it 526 override protected void updateShortcuts() { 527 if (applyShortcutsSettings()) { 528 Log.d("Shortcut actions loaded"); 529 } else { 530 Log.d("Saving default shortcuts"); 531 const(Action)[] actions; 532 actions ~= [ 533 ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, 534 ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, 535 ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, 536 ACTION_EDIT_PREFERENCES, 537 ACTION_FILE_NEW_SOURCE_FILE, ACTION_FILE_NEW_WORKSPACE, ACTION_FILE_NEW_PROJECT, ACTION_FILE_OPEN_WORKSPACE, ACTION_FILE_OPEN, 538 ACTION_FILE_SAVE, ACTION_FILE_SAVE_AS, ACTION_FILE_SAVE_ALL, ACTION_FILE_EXIT, 539 ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, 540 ACTION_PROJECT_SETTINGS, ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN, 541 ACTION_PROJECT_BUILD, ACTION_PROJECT_REBUILD, ACTION_PROJECT_CLEAN, ACTION_DEBUG_START, 542 ACTION_DEBUG_START_NO_DEBUG, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE, 543 ACTION_WINDOW_CLOSE_ALL_DOCUMENTS, ACTION_HELP_ABOUT]; 544 actions ~= STD_EDITOR_ACTIONS; 545 saveShortcutsSettings(actions); 546 } 547 } 548 549 /// create app toolbars 550 override protected ToolBarHost createToolbars() { 551 ToolBarHost res = new ToolBarHost(); 552 ToolBar tb; 553 tb = res.getOrAddToolbar("Standard"); 554 tb.addButtons(ACTION_FILE_OPEN, ACTION_FILE_SAVE, ACTION_SEPARATOR); 555 556 tb.addButtons(ACTION_DEBUG_START); 557 558 projectConfigurationCombo = new ToolBarComboBox("projectConfig", [ProjectConfiguration.DEFAULT_NAME.to!dstring]);//Updateable 559 projectConfigurationCombo.itemClick = delegate(Widget source, int index) { 560 if (currentWorkspace) { 561 currentWorkspace.setStartupProjectConfiguration(projectConfigurationCombo.selectedItem.to!string); 562 } 563 return true; 564 }; 565 projectConfigurationCombo.action = ACTION_PROJECT_CONFIGURATIONS; 566 tb.addControl(projectConfigurationCombo); 567 568 ToolBarComboBox cbBuildConfiguration = new ToolBarComboBox("buildConfig", ["Debug"d, "Release"d, "Unittest"d]); 569 cbBuildConfiguration.itemClick = delegate(Widget source, int index) { 570 if (currentWorkspace && index < 3) { 571 currentWorkspace.buildConfiguration = [BuildConfiguration.Debug, BuildConfiguration.Release, BuildConfiguration.Unittest][index]; 572 } 573 return true; 574 }; 575 cbBuildConfiguration.action = ACTION_BUILD_CONFIGURATIONS; 576 tb.addControl(cbBuildConfiguration); 577 tb.addButtons(ACTION_PROJECT_BUILD); 578 579 tb = res.getOrAddToolbar("Edit"); 580 tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR, 581 ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT); 582 tb = res.getOrAddToolbar("Debug"); 583 tb.addButtons(ACTION_DEBUG_STOP, ACTION_DEBUG_CONTINUE, ACTION_DEBUG_PAUSE); 584 return res; 585 } 586 587 /// override to handle specific actions state (e.g. change enabled state for supported actions) 588 override bool handleActionStateRequest(const Action a) { 589 switch (a.id) { 590 case IDEActions.EditPreferences: 591 return true; 592 case IDEActions.FileExit: 593 case IDEActions.FileOpen: 594 case IDEActions.WindowCloseAllDocuments: 595 case IDEActions.FileOpenWorkspace: 596 // disable when background operation in progress 597 if (!_currentBackgroundOperation) 598 a.state = ACTION_STATE_ENABLED; 599 else 600 a.state = ACTION_STATE_DISABLE; 601 return true; 602 case IDEActions.HelpAbout: 603 case StandardAction.OpenUrl: 604 // always enabled 605 a.state = ACTION_STATE_ENABLED; 606 return true; 607 case IDEActions.BuildProject: 608 case IDEActions.BuildWorkspace: 609 case IDEActions.RebuildProject: 610 case IDEActions.RebuildWorkspace: 611 case IDEActions.CleanProject: 612 case IDEActions.CleanWorkspace: 613 case IDEActions.UpdateProjectDependencies: 614 case IDEActions.RefreshProject: 615 case IDEActions.SetStartupProject: 616 case IDEActions.ProjectSettings: 617 // enable when project exists 618 if (currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation) 619 a.state = ACTION_STATE_ENABLED; 620 else 621 a.state = ACTION_STATE_DISABLE; 622 return true; 623 case IDEActions.DebugStop: 624 a.state = isExecutionActive ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE; 625 return true; 626 case IDEActions.DebugStart: 627 case IDEActions.DebugStartNoDebug: 628 if (!isExecutionActive && currentWorkspace && currentWorkspace.startupProject && !_currentBackgroundOperation) 629 a.state = ACTION_STATE_ENABLED; 630 else 631 a.state = ACTION_STATE_DISABLE; 632 return true; 633 case IDEActions.DebugContinue: 634 case IDEActions.DebugPause: 635 a.state = isExecutionActive && _execution.isDebugger ? ACTION_STATE_ENABLED : ACTION_STATE_DISABLE; 636 return true; 637 default: 638 return super.handleActionStateRequest(a); 639 } 640 } 641 642 FileDialog createFileDialog(UIString caption) { 643 FileDialog dlg = new FileDialog(caption, window, null); 644 dlg.filetypeIcons[".d"] = "text-d"; 645 dlg.filetypeIcons["dub.json"] = "project-d"; 646 dlg.filetypeIcons["package.json"] = "project-d"; 647 dlg.filetypeIcons[".dlangidews"] = "project-development"; 648 return dlg; 649 } 650 651 /// override to handle specific actions 652 override bool handleAction(const Action a) { 653 if (a) { 654 switch (a.id) { 655 case IDEActions.FileExit: 656 if (onCanClose()) 657 window.close(); 658 return true; 659 case IDEActions.HelpAbout: 660 window.showMessageBox(UIString("About DlangIDE"d), 661 UIString("DLangIDE\n(C) Vadim Lopatin, 2014\nhttp://github.com/buggins/dlangide\nIDE for D programming language written in D\nUses DlangUI library for GUI"d)); 662 return true; 663 case StandardAction.OpenUrl: 664 platform.openURL(a.stringParam); 665 return true; 666 case IDEActions.FileOpen: 667 UIString caption; 668 caption = "Open Text File"d; 669 FileDialog dlg = createFileDialog(caption); 670 dlg.addFilter(FileFilterEntry(UIString("Source files"d), "*.d;*.dd;*.ddoc;*.di;*.dh;*.json;*.xml;*.ini")); 671 dlg.addFilter(FileFilterEntry(UIString("All files"d), "*.*")); 672 dlg.dialogResult = delegate(Dialog dlg, const Action result) { 673 if (result.id == ACTION_OPEN.id) { 674 string filename = result.stringParam; 675 openSourceFile(filename); 676 } 677 }; 678 dlg.show(); 679 return true; 680 case IDEActions.BuildProject: 681 case IDEActions.BuildWorkspace: 682 buildProject(BuildOperation.Build, cast(Project)a.objectParam); 683 return true; 684 case IDEActions.RebuildProject: 685 case IDEActions.RebuildWorkspace: 686 buildProject(BuildOperation.Rebuild, cast(Project)a.objectParam); 687 return true; 688 case IDEActions.CleanProject: 689 case IDEActions.CleanWorkspace: 690 buildProject(BuildOperation.Clean, cast(Project)a.objectParam); 691 return true; 692 case IDEActions.DebugStart: 693 case IDEActions.DebugStartNoDebug: 694 case IDEActions.DebugContinue: 695 buildAndRunProject(cast(Project)a.objectParam); 696 return true; 697 case IDEActions.DebugStop: 698 stopExecution(); 699 return true; 700 case IDEActions.UpdateProjectDependencies: 701 buildProject(BuildOperation.Upgrade, cast(Project)a.objectParam); 702 return true; 703 case IDEActions.RefreshProject: 704 refreshWorkspace(); 705 return true; 706 case IDEActions.WindowCloseAllDocuments: 707 askForUnsavedEdits(delegate() { 708 closeAllDocuments(); 709 }); 710 return true; 711 case IDEActions.FileOpenWorkspace: 712 UIString caption; 713 caption = "Open Workspace or Project"d; 714 FileDialog dlg = createFileDialog(caption); 715 dlg.addFilter(FileFilterEntry(UIString("Workspace and project files"d), "*.dlangidews;dub.json;package.json")); 716 dlg.dialogResult = delegate(Dialog dlg, const Action result) { 717 if (result.id == ACTION_OPEN.id) { 718 string filename = result.stringParam; 719 if (filename.length) 720 openFileOrWorkspace(filename); 721 } 722 }; 723 dlg.show(); 724 return true; 725 case IDEActions.GoToDefinition: 726 Log.d("Trying to go to definition."); 727 currentEditor.editorTool.goToDefinition(currentEditor(), currentEditor.caretPos); 728 return true; 729 case IDEActions.GetCompletionSuggestions: 730 Log.d("Getting auto completion suggestions."); 731 auto results = currentEditor.editorTool.getCompletions(currentEditor, currentEditor.caretPos); 732 currentEditor.showCompletionPopup(results); 733 return true; 734 case IDEActions.EditPreferences: 735 showPreferences(); 736 return true; 737 case IDEActions.ProjectSettings: 738 showProjectSettings(); 739 return true; 740 case IDEActions.FindText: 741 Log.d("Opening Search Field"); 742 import dlangide.ui.searchPanel; 743 int searchPanelIndex = _logPanel.getTabs.tabIndex("search"); 744 SearchWidget searchPanel = null; 745 if(searchPanelIndex == -1) { 746 searchPanel = new SearchWidget("search", this); 747 _logPanel.getTabs.addTab( searchPanel, "Search"d, null, true); 748 } 749 else { 750 searchPanel = cast(SearchWidget) _logPanel.getTabs.tabBody(searchPanelIndex); 751 } 752 _logPanel.getTabs.selectTab("search"); 753 if(searchPanel !is null) { 754 searchPanel.focus(); 755 } 756 return true; 757 case IDEActions.FileNewWorkspace: 758 createNewProject(true); 759 return true; 760 case IDEActions.FileNewProject: 761 createNewProject(false); 762 return true; 763 case IDEActions.FileNew: 764 addProjectItem(a.objectParam); 765 return true; 766 case IDEActions.ProjectFolderRemoveItem: 767 removeProjectItem(a.objectParam); 768 return true; 769 case IDEActions.ProjectFolderRefresh: 770 refreshProjectItem(a.objectParam); 771 return true; 772 case IDEActions.CloseWorkspace: 773 closeWorkspace(); 774 return true; 775 default: 776 return super.handleAction(a); 777 } 778 } 779 return false; 780 } 781 782 @property ProjectSourceFile currentEditorSourceFile() { 783 TabItem tab = _tabs.selectedTab; 784 if (tab) { 785 return cast(ProjectSourceFile)tab.objectParam; 786 } 787 return null; 788 } 789 790 void closeWorkspace() { 791 askForUnsavedEdits(delegate() { 792 setWorkspace(null); 793 showHomeScreen(); 794 }); 795 } 796 797 void refreshProjectItem(const Object obj) { 798 if (currentWorkspace is null) 799 return; 800 Project project; 801 ProjectFolder folder; 802 if (cast(Workspace)obj) { 803 Workspace ws = cast(Workspace)obj; 804 ws.refresh(); 805 refreshWorkspace(); 806 } else if (cast(Project)obj) { 807 project = cast(Project)obj; 808 } else if (cast(ProjectFolder)obj) { 809 folder = cast(ProjectFolder)obj; 810 project = folder.project; 811 } else if (cast(ProjectSourceFile)obj) { 812 ProjectSourceFile srcfile = cast(ProjectSourceFile)obj; 813 folder = cast(ProjectFolder)srcfile.parent; 814 project = srcfile.project; 815 } else { 816 ProjectSourceFile srcfile = currentEditorSourceFile; 817 if (srcfile) { 818 folder = cast(ProjectFolder)srcfile.parent; 819 project = srcfile.project; 820 } 821 } 822 if (project) { 823 project.refresh(); 824 refreshWorkspace(); 825 } 826 } 827 828 void removeProjectItem(const Object obj) { 829 if (currentWorkspace is null) 830 return; 831 ProjectSourceFile srcfile = cast(ProjectSourceFile)obj; 832 if (!srcfile) 833 return; 834 Project project = srcfile.project; 835 if (!project) 836 return; 837 window.showMessageBox(UIString("Remove file"d), 838 UIString("Do you want to remove file "d ~ srcfile.name ~ "?"), 839 [ACTION_YES, ACTION_NO], 840 1, delegate(const Action result) { 841 if (result == StandardAction.Yes) { 842 // save and close 843 try { 844 import std.file : remove; 845 closeTab(srcfile.filename); 846 remove(srcfile.filename); 847 project.refresh(); 848 refreshWorkspace(); 849 } catch (Exception e) { 850 Log.e("Error while removing file"); 851 } 852 } 853 // else ignore 854 return true; 855 }); 856 857 } 858 859 void addProjectItem(const Object obj) { 860 if (currentWorkspace is null) 861 return; 862 Project project; 863 ProjectFolder folder; 864 if (cast(Project)obj) { 865 project = cast(Project)obj; 866 } else if (cast(ProjectFolder)obj) { 867 folder = cast(ProjectFolder)obj; 868 project = folder.project; 869 } else if (cast(ProjectSourceFile)obj) { 870 ProjectSourceFile srcfile = cast(ProjectSourceFile)obj; 871 folder = cast(ProjectFolder)srcfile.parent; 872 project = srcfile.project; 873 } else { 874 ProjectSourceFile srcfile = currentEditorSourceFile; 875 if (srcfile) { 876 folder = cast(ProjectFolder)srcfile.parent; 877 project = srcfile.project; 878 } 879 } 880 if (project && folder && project.workspace is currentWorkspace) { 881 NewFileDlg dlg = new NewFileDlg(this, project, folder); 882 dlg.dialogResult = delegate(Dialog dlg, const Action result) { 883 if (result.id == ACTION_FILE_NEW_SOURCE_FILE.id) { 884 FileCreationResult res = cast(FileCreationResult)result.objectParam; 885 if (res) { 886 //res.project.reload(); 887 res.project.refresh(); 888 refreshWorkspace(); 889 if (isSupportedSourceTextFileFormat(res.filename)) { 890 openSourceFile(res.filename, null, true); 891 } 892 } 893 } 894 }; 895 dlg.show(); 896 } 897 } 898 899 void createNewProject(bool newWorkspace) { 900 if (currentWorkspace is null) 901 newWorkspace = true; 902 NewProjectDlg dlg = new NewProjectDlg(this, newWorkspace, currentWorkspace); 903 dlg.dialogResult = delegate(Dialog dlg, const Action result) { 904 if (result.id == ACTION_FILE_NEW_PROJECT.id || result.id == ACTION_FILE_NEW_WORKSPACE.id) { 905 //Log.d("settings after edit:\n", s.toJSON(true)); 906 ProjectCreationResult res = cast(ProjectCreationResult)result.objectParam; 907 if (res) { 908 // open workspace/project 909 if (currentWorkspace is null || res.workspace !is currentWorkspace) { 910 // open new workspace 911 setWorkspace(res.workspace); 912 refreshWorkspace(); 913 hideHomeScreen(); 914 } else { 915 // project added to current workspace 916 loadProject(res.project); 917 refreshWorkspace(); 918 hideHomeScreen(); 919 } 920 } 921 } 922 }; 923 dlg.show(); 924 } 925 926 void showPreferences() { 927 //Log.d("settings before copy:\n", _settings.setting.toJSON(true)); 928 Setting s = _settings.copySettings(); 929 //Log.d("settings after copy:\n", s.toJSON(true)); 930 SettingsDialog dlg = new SettingsDialog(UIString("DlangIDE settings"d), window, s, createSettingsPages()); 931 dlg.dialogResult = delegate(Dialog dlg, const Action result) { 932 if (result.id == ACTION_APPLY.id) { 933 //Log.d("settings after edit:\n", s.toJSON(true)); 934 _settings.applySettings(s); 935 applySettings(_settings); 936 _settings.save(); 937 } 938 }; 939 dlg.show(); 940 } 941 942 void showProjectSettings() { 943 if (!currentWorkspace) 944 return; 945 Project project = currentWorkspace.startupProject; 946 if (!project) 947 return; 948 Setting s = project.settings.copySettings(); 949 SettingsDialog dlg = new SettingsDialog(UIString(project.name ~ " settings"d), window, s, createProjectSettingsPages()); 950 dlg.dialogResult = delegate(Dialog dlg, const Action result) { 951 if (result.id == ACTION_APPLY.id) { 952 //Log.d("settings after edit:\n", s.toJSON(true)); 953 project.settings.applySettings(s); 954 project.settings.save(); 955 } 956 }; 957 dlg.show(); 958 } 959 960 void applySettings(IDESettings settings) { 961 for (int i = _tabs.tabCount - 1; i >= 0; i--) { 962 DSourceEdit ed = cast(DSourceEdit)_tabs.tabBody(i); 963 if (ed) { 964 applySettings(ed, settings); 965 } 966 } 967 FontManager.fontGamma = settings.fontGamma; 968 FontManager.hintingMode = settings.hintingMode; 969 FontManager.minAnitialiasedFontSize = settings.minAntialiasedFontSize; 970 Platform.instance.uiLanguage = settings.uiLanguage; 971 Platform.instance.uiTheme = settings.uiTheme; 972 requestLayout(); 973 } 974 975 void applySettings(DSourceEdit editor, IDESettings settings) { 976 editor.settings(settings).applySettings(); 977 } 978 979 private bool loadProject(Project project) { 980 if (!project.load()) { 981 _logPanel.logLine("Cannot read project " ~ project.filename); 982 window.showMessageBox(UIString("Cannot open project"d), UIString("Error occured while opening project "d ~ toUTF32(project.filename))); 983 return false; 984 } 985 _logPanel.logLine(toUTF32("Project file " ~ project.filename ~ " is opened ok")); 986 return true; 987 } 988 989 void openFileOrWorkspace(string filename) { 990 if (filename.isWorkspaceFile) { 991 Workspace ws = new Workspace(this); 992 if (ws.load(filename)) { 993 askForUnsavedEdits(delegate() { 994 setWorkspace(ws); 995 hideHomeScreen(); 996 }); 997 } else { 998 window.showMessageBox(UIString("Cannot open workspace"d), UIString("Error occured while opening workspace"d)); 999 return; 1000 } 1001 } else if (filename.isProjectFile) { 1002 _logPanel.clear(); 1003 _logPanel.logLine("Trying to open project from " ~ filename); 1004 Project project = new Project(currentWorkspace, filename); 1005 string defWsFile = project.defWorkspaceFile; 1006 if (currentWorkspace) { 1007 Project existing = currentWorkspace.findProject(project.filename); 1008 if (existing) { 1009 _logPanel.logLine("This project already exists in current workspace"); 1010 window.showMessageBox(UIString("Open project"d), UIString("Project is already in workspace"d)); 1011 return; 1012 } 1013 window.showMessageBox(UIString("Open project"d), UIString("Do you want to create new workspace or use current one?"d), 1014 [ACTION_ADD_TO_CURRENT_WORKSPACE, ACTION_CREATE_NEW_WORKSPACE, ACTION_CANCEL], 0, delegate(const Action result) { 1015 if (result.id == IDEActions.CreateNewWorkspace) { 1016 // new ws 1017 createNewWorkspaceForExistingProject(project); 1018 hideHomeScreen(); 1019 } else if (result.id == IDEActions.AddToCurrentWorkspace) { 1020 // add to current 1021 currentWorkspace.addProject(project); 1022 loadProject(project); 1023 currentWorkspace.save(); 1024 refreshWorkspace(); 1025 hideHomeScreen(); 1026 } 1027 return true; 1028 }); 1029 } else { 1030 // new workspace file 1031 createNewWorkspaceForExistingProject(project); 1032 } 1033 } else { 1034 _logPanel.logLine("File is not recognized as DlangIDE project or workspace file"); 1035 window.showMessageBox(UIString("Invalid workspace file"d), UIString("This file is not a valid workspace or project file"d)); 1036 } 1037 } 1038 1039 void refreshWorkspace() { 1040 _logPanel.logLine("Refreshing workspace"); 1041 _wsPanel.reloadItems(); 1042 closeRemovedDocuments(); 1043 } 1044 1045 void createNewWorkspaceForExistingProject(Project project) { 1046 string defWsFile = project.defWorkspaceFile; 1047 _logPanel.logLine("Creating new workspace " ~ defWsFile); 1048 // new ws 1049 Workspace ws = new Workspace(this); 1050 ws.name = project.name; 1051 ws.description = project.description; 1052 ws.addProject(project); 1053 loadProject(project); 1054 ws.save(defWsFile); 1055 setWorkspace(ws); 1056 _logPanel.logLine("Done"); 1057 } 1058 1059 //bool loadWorkspace(string path) { 1060 // // testing workspace loader 1061 // Workspace ws = new Workspace(); 1062 // ws.load(path); 1063 // setWorkspace(ws); 1064 // //ws.save(ws.filename ~ ".bak"); 1065 // return true; 1066 //} 1067 1068 void setWorkspace(Workspace ws) { 1069 closeAllDocuments(); 1070 currentWorkspace = ws; 1071 _wsPanel.workspace = ws; 1072 requestActionsUpdate(); 1073 if (ws && ws.startupProject && ws.startupProject.mainSourceFile) { 1074 openSourceFile(ws.startupProject.mainSourceFile.filename); 1075 _tabs.setFocus(); 1076 } 1077 } 1078 1079 void buildProject(BuildOperation buildOp, Project project, BuildResultListener listener = null) { 1080 if (!currentWorkspace) { 1081 _logPanel.logLine("No workspace is opened"); 1082 return; 1083 } 1084 if (!project) 1085 project = currentWorkspace.startupProject; 1086 if (!project) { 1087 _logPanel.logLine("No project is opened"); 1088 return; 1089 } 1090 ProjectSettings projectSettings = project.settings; 1091 string toolchain = projectSettings.getToolchain(_settings); 1092 string arch = projectSettings.getArch(_settings); 1093 bool verbose = projectSettings.buildVerbose; 1094 Builder op = new Builder(this, project, _logPanel, currentWorkspace.projectConfiguration, currentWorkspace.buildConfiguration, buildOp, 1095 verbose, 1096 toolchain, 1097 arch, 1098 listener); 1099 setBackgroundOperation(op); 1100 } 1101 1102 /// updates list of available configurations 1103 void setProjectConfigurations(dstring[] items) { 1104 projectConfigurationCombo.items = items; 1105 } 1106 1107 /// handle files dropped to application window 1108 void onFilesDropped(string[] filenames) { 1109 //Log.d("onFilesDropped(", filenames, ")"); 1110 bool first = true; 1111 for (int i = 0; i < filenames.length; i++) { 1112 openSourceFile(filenames[i], null, first); 1113 first = false; 1114 } 1115 } 1116 1117 /// return false to prevent closing 1118 bool onCanClose() { 1119 askForUnsavedEdits(delegate() { 1120 window.close(); 1121 }); 1122 return false; 1123 } 1124 /// called when main window is closing 1125 void onWindowClose() { 1126 Log.i("onWindowClose()"); 1127 stopExecution(); 1128 if (_dcdServer) { 1129 if (_dcdServer.isRunning) 1130 _dcdServer.stop(); 1131 destroy(_dcdServer); 1132 _dcdServer = null; 1133 } 1134 } 1135 } 1136