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.tools.d.dsyntaxhighlighter; 17 18 import std.algorithm; 19 20 21 /// DIDE source file editor 22 class DSourceEdit : SourceEdit { 23 this(string ID) { 24 super(ID); 25 styleId = null; 26 backgroundColor = 0xFFFFFF; 27 setTokenHightlightColor(TokenCategory.Comment, 0x008000); // green 28 setTokenHightlightColor(TokenCategory.Keyword, 0x0000FF); // blue 29 setTokenHightlightColor(TokenCategory.String, 0xA31515); // brown 30 setTokenHightlightColor(TokenCategory.Character, 0xA31515); // brown 31 setTokenHightlightColor(TokenCategory.Error, 0xFF0000); // red 32 setTokenHightlightColor(TokenCategory.Comment_Documentation, 0x206000); 33 //setTokenHightlightColor(TokenCategory.Identifier, 0x206000); // no colors 34 MenuItem editPopupItem = new MenuItem(null); 35 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); 36 //ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS 37 popupMenu = editPopupItem; 38 showIcons = true; 39 showFolding = true; 40 } 41 this() { 42 this("SRCEDIT"); 43 } 44 protected ProjectSourceFile _projectSourceFile; 45 @property ProjectSourceFile projectSourceFile() { return _projectSourceFile; } 46 /// load by filename 47 override bool load(string fn) { 48 _projectSourceFile = null; 49 bool res = super.load(fn); 50 setHighlighter(); 51 return res; 52 } 53 54 @property bool isDSourceFile() { 55 return filename.endsWith(".d") || filename.endsWith(".dd") || filename.endsWith(".dh") || filename.endsWith(".ddoc"); 56 } 57 58 void setHighlighter() { 59 if (isDSourceFile) { 60 content.syntaxHighlighter = new SimpleDSyntaxHighlighter(filename); 61 } else { 62 content.syntaxHighlighter = null; 63 } 64 } 65 66 /// returns project import paths - if file from project is opened in current editor 67 string[] importPaths() { 68 if (_projectSourceFile) 69 return _projectSourceFile.project.sourcePaths ~ _projectSourceFile.project.builderSourcePaths; 70 return null; 71 } 72 73 /// load by project item 74 bool load(ProjectSourceFile f) { 75 if (!load(f.filename)) { 76 _projectSourceFile = null; 77 return false; 78 } 79 _projectSourceFile = f; 80 setHighlighter(); 81 return true; 82 } 83 84 /// save to the same file 85 bool save() { 86 return _content.save(); 87 } 88 89 void insertCompletion(dstring completionText) { 90 TextRange range; 91 TextPosition p = getCaretPosition; 92 range.start = range.end = p; 93 dstring lineText = content.line(p.line); 94 dchar prevChar = p.pos > 0 ? lineText[p.pos - 1] : 0; 95 dchar nextChar = p.pos < lineText.length ? lineText[p.pos] : 0; 96 if (isIdentMiddleChar(prevChar)) { 97 while(range.start.pos > 0 && isIdentMiddleChar(lineText[range.start.pos - 1])) 98 range.start.pos--; 99 if (isIdentMiddleChar(nextChar)) { 100 while(range.end.pos < lineText.length && isIdentMiddleChar(lineText[range.end.pos])) 101 range.end.pos++; 102 } 103 } 104 EditOperation edit = new EditOperation(EditAction.Replace, range, completionText); 105 _content.performOperation(edit, this); 106 setFocus(); 107 } 108 109 /// override to handle specific actions 110 override bool handleAction(const Action a) { 111 import ddc.lexer.tokenizer; 112 if (a) { 113 switch (a.id) { 114 case IDEActions.FileSave: 115 save(); 116 return true; 117 case IDEActions.InsertCompletion: 118 insertCompletion(a.label); 119 return true; 120 default: 121 break; 122 } 123 } 124 return super.handleAction(a); 125 } 126 127 /// override to handle specific actions state (e.g. change enabled state for supported actions) 128 override bool handleActionStateRequest(const Action a) { 129 switch (a.id) { 130 case IDEActions.GoToDefinition: 131 case IDEActions.GetCompletionSuggestions: 132 if (isDSourceFile) 133 a.state = ACTION_STATE_ENABLED; 134 else 135 a.state = ACTION_STATE_DISABLE; 136 return true; 137 default: 138 return super.handleActionStateRequest(a); 139 } 140 } 141 142 void showCompletionPopup(dstring[] suggestions) { 143 144 if(suggestions.length == 0) { 145 setFocus(); 146 return; 147 } 148 149 if (suggestions.length == 1) { 150 insertCompletion(suggestions[0]); 151 return; 152 } 153 154 MenuItem completionPopupItems = new MenuItem(null); 155 //Add all the suggestions. 156 foreach(int i, dstring suggestion ; suggestions) { 157 auto action = new Action(IDEActions.InsertCompletion, suggestion); 158 completionPopupItems.add(action); 159 } 160 completionPopupItems.updateActionState(this); 161 162 PopupMenu popupMenu = new PopupMenu(completionPopupItems); 163 popupMenu.onMenuItemActionListener = this; 164 popupMenu.maxHeight(400); 165 popupMenu.selectItem(0); 166 167 PopupWidget popup = window.showPopup(popupMenu, this, PopupAlign.Point | PopupAlign.Right, textPosToClient(_caretPos).left + left + _leftPaneWidth, textPosToClient(_caretPos).top + top + margins.top); 168 popup.setFocus(); 169 popup.onPopupCloseListener = delegate(PopupWidget source) { setFocus(); }; 170 popup.flags = PopupFlags.CloseOnClickOutside; 171 172 Log.d("Showing popup at ", textPosToClient(_caretPos).left, " ", textPosToClient(_caretPos).top); 173 } 174 175 TextPosition getCaretPosition() { 176 return _caretPos; 177 } 178 179 /// change caret position and ensure it is visible 180 void setCaretPos(int line, int column) 181 { 182 _caretPos = TextPosition(line,column); 183 invalidate(); 184 ensureCaretVisible(); 185 } 186 }