1 module dlangide.ui.dsourceedit; 2 3 import dlangui.core.logger; 4 import dlangui.widgets.editors; 5 import dlangui.widgets.srcedit; 6 import dlangui.widgets.menu; 7 import dlangui.widgets.popup; 8 9 import ddc.lexer.textsource; 10 import ddc.lexer.exceptions; 11 import ddc.lexer.tokenizer; 12 13 import dlangide.workspace.workspace; 14 import dlangide.workspace.project; 15 import dlangide.ui.commands; 16 import dlangide.ui.settings; 17 import dlangide.tools.d.dsyntax; 18 import dlangide.tools.editorTool; 19 20 import std.algorithm; 21 22 23 /// DIDE source file editor 24 class DSourceEdit : SourceEdit { 25 this(string ID) { 26 super(ID); 27 styleId = null; 28 backgroundColor = style.customColor("edit_background"); 29 onThemeChanged(); 30 //setTokenHightlightColor(TokenCategory.Identifier, 0x206000); // no colors 31 MenuItem editPopupItem = new MenuItem(null); 32 editPopupItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_GET_COMPLETIONS, ACTION_GO_TO_DEFINITION); 33 //ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS 34 popupMenu = editPopupItem; 35 showIcons = true; 36 showFolding = true; 37 } 38 this() { 39 this("SRCEDIT"); 40 } 41 42 /// handle theme change: e.g. reload some themed resources 43 override void onThemeChanged() { 44 backgroundColor = style.customColor("edit_background"); 45 setTokenHightlightColor(TokenCategory.Comment, style.customColor("syntax_highlight_comment")); // green 46 setTokenHightlightColor(TokenCategory.Keyword, style.customColor("syntax_highlight_keyword")); // blue 47 setTokenHightlightColor(TokenCategory.String, style.customColor("syntax_highlight_string")); // brown 48 setTokenHightlightColor(TokenCategory.Character, style.customColor("syntax_highlight_character")); // brown 49 setTokenHightlightColor(TokenCategory.Error, style.customColor("syntax_highlight_error")); // red 50 setTokenHightlightColor(TokenCategory.Comment_Documentation, style.customColor("syntax_highlight_comment_documentation")); 51 52 super.onThemeChanged(); 53 } 54 55 protected IDESettings _settings; 56 @property DSourceEdit settings(IDESettings s) { 57 _settings = s; 58 return this; 59 } 60 @property IDESettings settings() { 61 return _settings; 62 } 63 void applySettings() { 64 if (!_settings) 65 return; 66 tabSize = _settings.tabSize; 67 useSpacesForTabs = _settings.useSpacesForTabs; 68 smartIndents = _settings.smartIndents; 69 smartIndentsAfterPaste = _settings.smartIndentsAfterPaste; 70 } 71 72 protected EditorTool _editorTool; 73 @property EditorTool editorTool() { return _editorTool; } 74 @property EditorTool editorTool(EditorTool tool) { return _editorTool = tool; }; 75 76 protected ProjectSourceFile _projectSourceFile; 77 @property ProjectSourceFile projectSourceFile() { return _projectSourceFile; } 78 /// load by filename 79 override bool load(string fn) { 80 _projectSourceFile = null; 81 bool res = super.load(fn); 82 setSyntaxSupport(); 83 return res; 84 } 85 86 @property bool isDSourceFile() { 87 return filename.endsWith(".d") || filename.endsWith(".dd") || filename.endsWith(".dh") || filename.endsWith(".ddoc"); 88 } 89 90 void setSyntaxSupport() { 91 if (isDSourceFile) { 92 content.syntaxSupport = new SimpleDSyntaxSupport(filename); 93 } else { 94 content.syntaxSupport = null; 95 } 96 } 97 98 /// returns project import paths - if file from project is opened in current editor 99 string[] importPaths() { 100 if (_projectSourceFile) 101 return _projectSourceFile.project.importPaths; 102 return null; 103 } 104 105 /// load by project item 106 bool load(ProjectSourceFile f) { 107 if (!load(f.filename)) { 108 _projectSourceFile = null; 109 return false; 110 } 111 _projectSourceFile = f; 112 setSyntaxSupport(); 113 return true; 114 } 115 116 /// save to the same file 117 bool save() { 118 return _content.save(); 119 } 120 121 void insertCompletion(dstring completionText) { 122 TextRange range; 123 TextPosition p = caretPos; 124 range.start = range.end = p; 125 dstring lineText = content.line(p.line); 126 dchar prevChar = p.pos > 0 ? lineText[p.pos - 1] : 0; 127 dchar nextChar = p.pos < lineText.length ? lineText[p.pos] : 0; 128 if (isIdentMiddleChar(prevChar)) { 129 while(range.start.pos > 0 && isIdentMiddleChar(lineText[range.start.pos - 1])) 130 range.start.pos--; 131 if (isIdentMiddleChar(nextChar)) { 132 while(range.end.pos < lineText.length && isIdentMiddleChar(lineText[range.end.pos])) 133 range.end.pos++; 134 } 135 } 136 EditOperation edit = new EditOperation(EditAction.Replace, range, completionText); 137 _content.performOperation(edit, this); 138 setFocus(); 139 } 140 141 /// override to handle specific actions 142 override bool handleAction(const Action a) { 143 import ddc.lexer.tokenizer; 144 if (a) { 145 switch (a.id) { 146 case IDEActions.FileSave: 147 save(); 148 return true; 149 case IDEActions.InsertCompletion: 150 insertCompletion(a.label); 151 return true; 152 default: 153 break; 154 } 155 } 156 return super.handleAction(a); 157 } 158 159 /// override to handle specific actions state (e.g. change enabled state for supported actions) 160 override bool handleActionStateRequest(const Action a) { 161 switch (a.id) { 162 case IDEActions.GoToDefinition: 163 case IDEActions.GetCompletionSuggestions: 164 if (isDSourceFile) 165 a.state = ACTION_STATE_ENABLED; 166 else 167 a.state = ACTION_STATE_DISABLE; 168 return true; 169 default: 170 return super.handleActionStateRequest(a); 171 } 172 } 173 174 void showCompletionPopup(dstring[] suggestions) { 175 176 if(suggestions.length == 0) { 177 setFocus(); 178 return; 179 } 180 181 if (suggestions.length == 1) { 182 insertCompletion(suggestions[0]); 183 return; 184 } 185 186 MenuItem completionPopupItems = new MenuItem(null); 187 //Add all the suggestions. 188 foreach(int i, dstring suggestion ; suggestions) { 189 auto action = new Action(IDEActions.InsertCompletion, suggestion); 190 completionPopupItems.add(action); 191 } 192 completionPopupItems.updateActionState(this); 193 194 PopupMenu popupMenu = new PopupMenu(completionPopupItems); 195 popupMenu.menuItemAction = this; 196 popupMenu.maxHeight(400); 197 popupMenu.selectItem(0); 198 199 PopupWidget popup = window.showPopup(popupMenu, this, PopupAlign.Point | PopupAlign.Right, textPosToClient(_caretPos).left + left + _leftPaneWidth, textPosToClient(_caretPos).top + top + margins.top); 200 popup.setFocus(); 201 popup.popupClosed = delegate(PopupWidget source) { setFocus(); }; 202 popup.flags = PopupFlags.CloseOnClickOutside; 203 204 Log.d("Showing popup at ", textPosToClient(_caretPos).left, " ", textPosToClient(_caretPos).top); 205 } 206 207 }