1 module dlangide.tools.d.deditorTool;
2 
3 import dlangide.tools.editorTool;
4 import dlangide.tools.d.dcdinterface;
5 import dlangide.ui.dsourceedit;
6 import dlangui.widgets.editors;
7 import dlangide.ui.frame;
8 import std.stdio;
9 import std.string;
10 import std.utf;
11 import dlangui.core.logger;
12 
13 import std.conv;
14 
15 // TODO: async operation in background thread
16 // TODO: effective caretPositionToByteOffset/byteOffsetToCaret impl
17 
18 class DEditorTool : EditorTool 
19 {
20     this(IDEFrame frame) {
21         super(frame);
22     }
23 
24     ~this() {
25         cancelGoToDefinition();
26         cancelGetDocComments();
27         cancelGetCompletions();
28     }
29 
30     static bool isIdentChar(char ch) {
31         return ch == '_' || (ch >= 'a' && ch <='z') || (ch >= 'A' && ch <='Z') || ((ch & 0x80) != 0);
32     }
33     static bool isAtWord(string content, size_t byteOffset) {
34         if (byteOffset >= content.length)
35             return false;
36         if (isIdentChar(content[byteOffset]))
37             return true;
38         if (byteOffset > 0 && isIdentChar(content[byteOffset - 1]))
39             return true;
40         if (byteOffset + 1 < content.length && isIdentChar(content[byteOffset + 1]))
41             return true;
42         return false;
43     }
44 
45     DCDTask _getDocCommentsTask;
46     override void getDocComments(DSourceEdit editor, TextPosition caretPosition, void delegate(string[]) callback) {
47         cancelGetDocComments();
48         string[] importPaths = editor.importPaths();
49         string content = toUTF8(editor.text);
50         auto byteOffset = caretPositionToByteOffset(content, caretPosition);
51         if (!isAtWord(content, byteOffset))
52             return;
53         _getDocCommentsTask = _frame.dcdInterface.getDocComments(editor.window, importPaths, editor.filename, content, byteOffset, delegate(DocCommentsResultSet output) {
54             if(output.result == DCDResult.SUCCESS) {
55                 auto doc = output.docComments;
56                 Log.d("Doc comments: ", doc);
57                 if (doc.length)
58                     callback(doc);
59                 _getDocCommentsTask = null;
60             }
61         });
62     }
63 
64     override void cancelGetDocComments() {
65         if (_getDocCommentsTask) {
66             _getDocCommentsTask.cancel();
67             _getDocCommentsTask = null;
68         }
69     }
70 
71     override void cancelGoToDefinition() {
72         if (_goToDefinitionTask) {
73             _goToDefinitionTask.cancel();
74             _goToDefinitionTask = null;
75         }
76     }
77 
78     override void cancelGetCompletions() {
79         if (_getCompletionsTask) {
80             _getCompletionsTask.cancel();
81             _getCompletionsTask = null;
82         }
83     }
84 
85     DCDTask _goToDefinitionTask;
86     override void goToDefinition(DSourceEdit editor, TextPosition caretPosition) {
87         cancelGoToDefinition();
88         string[] importPaths = editor.importPaths();
89         string content = toUTF8(editor.text);
90         auto byteOffset = caretPositionToByteOffset(content, caretPosition);
91 
92 
93         _goToDefinitionTask = _frame.dcdInterface.goToDefinition(editor.window, importPaths, editor.filename, content, byteOffset, delegate(FindDeclarationResultSet output) {
94             // handle result
95             switch(output.result) {
96                 //TODO: Show dialog
97                 case DCDResult.FAIL:
98                 case DCDResult.NO_RESULT:
99                     editor.setFocus();
100                     break;
101                 case DCDResult.SUCCESS:
102                     auto fileName = output.fileName;
103                     if(fileName.indexOf("stdin") == 0) {
104                         Log.d("Declaration is in current file. Jumping to it.");
105                     } else {
106                         //Must open file first to get the content for finding the correct caret position.
107                         if (!_frame.openSourceFile(to!string(fileName)))
108                             break;
109                         if (_frame.currentEditor.parent)
110                             _frame.currentEditor.parent.layout(_frame.currentEditor.parent.pos);
111                         content = toUTF8(_frame.currentEditor.text);
112                     }
113                     auto target = to!int(output.offset);
114                     auto destPos = byteOffsetToCaret(content, target);
115                     _frame.currentEditor.setCaretPos(destPos.line,destPos.pos, true, true);
116                     _frame.currentEditor.setFocus();
117                     break;
118                 default:
119                     break;
120             }
121             _goToDefinitionTask = null;
122         });
123 
124     }
125 
126     DCDTask _getCompletionsTask;
127     override void getCompletions(DSourceEdit editor, TextPosition caretPosition, void delegate(dstring[] completions, string[] icons) callback) {
128         string[] importPaths = editor.importPaths();
129 
130         string content = toUTF8(editor.text);
131         auto byteOffset = caretPositionToByteOffset(content, caretPosition);
132         _getCompletionsTask = _frame.dcdInterface.getCompletions(editor.window, importPaths, editor.filename, content, byteOffset, delegate(CompletionResultSet output) {
133             string[] icons;
134             dstring[] labels;
135             foreach(index, label; output.output) {
136                 string iconId;
137                 char ch = index < output.completionKinds.length ? output.completionKinds[index] : 0;
138                 switch(ch) {
139                     case 'c': // - class name
140                         iconId = "symbol-class";
141                         break;
142                     case 'i': // - interface name
143                         iconId = "symbol-interface";
144                         break;
145                     case 's': // - struct name
146                         iconId = "symbol-struct";
147                         break;
148                     case 'u': // - union name
149                         iconId = "symbol-union";
150                         break;
151                     case 'v': // - variable name
152                         iconId = "symbol-var";
153                         break;
154                     case 'm': // - member variable name
155                         iconId = "symbol-membervar";
156                         break;
157                     case 'k': // - keyword, built-in version, scope statement
158                         iconId = "symbol-keyword";
159                         break;
160                     case 'f': // - function or method
161                         iconId = "symbol-function";
162                         break;
163                     case 'g': // - enum name
164                         iconId = "symbol-enum";
165                         break;
166                     case 'e': // - enum member
167                         iconId = "symbol-enum";
168                         break;
169                     case 'P': // - package name
170                         iconId = "symbol-package";
171                         break;
172                     case 'M': // - module name
173                         iconId = "symbol-module";
174                         break;
175                     case 'a': // - array
176                         iconId = "symbol-array";
177                         break;
178                     case 'A': // - associative array
179                         iconId = "symbol-array";
180                         break;
181                     case 'l': // - alias name
182                         iconId = "symbol-alias";
183                         break;
184                     case 't': // - template name
185                         iconId = "symbol-template";
186                         break;
187                     case 'T': // - mixin template name
188                         iconId = "symbol-mixintemplate";
189                         break;
190                     default:
191                         break;
192                 }
193                 if (!iconId)
194                     iconId = "symbol-other";
195                 icons ~= iconId;
196                 labels ~= label;
197             }
198             callback(labels, icons);
199             _getCompletionsTask = null;
200         });
201     }
202 
203 private:
204 
205 }
206 
207 /// convert caret position to byte offset in utf8 content
208 int caretPositionToByteOffset(string content, TextPosition caretPosition) {
209     auto line = 0;
210     auto pos = 0;
211     auto bytes = 0;
212     foreach(c; content) {
213         if(line == caretPosition.line) {
214             if(pos == caretPosition.pos)
215                 break;
216             pos++;
217         } else if (line > caretPosition.line) {
218             break;
219         }
220         bytes++;
221         if(c == '\n') {
222             line++;
223             pos = 0;
224         }
225     }
226     return bytes;
227 }
228 
229 /// convert byte offset in utf8 content to caret position
230 TextPosition byteOffsetToCaret(string content, int byteOffset) {
231     int bytes = 0;
232     int line = 0;
233     int pos = 0;
234     TextPosition textPos;
235     foreach(c; content) {
236         if(bytes == byteOffset) {
237             //We all good.
238             textPos.line = line;
239             textPos.pos = pos;
240             return textPos;
241         }
242         bytes++;
243         if(c == '\n')
244         {
245             line++;
246             pos = 0;
247         }
248         else {
249             pos++;
250         }
251     }
252     return textPos;
253 }