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 }