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 }