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(_frame.settings);
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             Log.d("Cancelling getDocComments()");
67             _getDocCommentsTask.cancel();
68             _getDocCommentsTask = null;
69         }
70     }
71 
72     override void cancelGoToDefinition() {
73         if (_goToDefinitionTask) {
74             Log.d("Cancelling goToDefinition()");
75             _goToDefinitionTask.cancel();
76             _goToDefinitionTask = null;
77         }
78     }
79 
80     override void cancelGetCompletions() {
81         if (_getCompletionsTask) {
82             Log.d("Cancelling getCompletions()");
83             _getCompletionsTask.cancel();
84             _getCompletionsTask = null;
85         }
86     }
87 
88     DCDTask _goToDefinitionTask;
89     override void goToDefinition(DSourceEdit editor, TextPosition caretPosition) {
90         cancelGoToDefinition();
91         string[] importPaths = editor.importPaths(_frame.settings);
92         string content = toUTF8(editor.text);
93         auto byteOffset = caretPositionToByteOffset(content, caretPosition);
94 
95 
96         _goToDefinitionTask = _frame.dcdInterface.goToDefinition(editor.window, importPaths, editor.filename, content, byteOffset, delegate(FindDeclarationResultSet output) {
97             // handle result
98             switch(output.result) {
99                 //TODO: Show dialog
100                 case DCDResult.FAIL:
101                 case DCDResult.NO_RESULT:
102                     editor.setFocus();
103                     break;
104                 case DCDResult.SUCCESS:
105                     auto fileName = output.fileName;
106                     if(fileName.indexOf("stdin") == 0) {
107                         Log.d("Declaration is in current file. Jumping to it.");
108                     } else {
109                         //Must open file first to get the content for finding the correct caret position.
110                         if (!_frame.openSourceFile(to!string(fileName)))
111                             break;
112                         if (_frame.currentEditor.parent)
113                             _frame.currentEditor.parent.layout(_frame.currentEditor.parent.pos);
114                         content = toUTF8(_frame.currentEditor.text);
115                     }
116                     auto target = to!int(output.offset);
117                     auto destPos = byteOffsetToCaret(content, target);
118                     _frame.currentEditor.setCaretPos(destPos.line,destPos.pos, true, true);
119                     _frame.currentEditor.setFocus();
120                     break;
121                 default:
122                     break;
123             }
124             _goToDefinitionTask = null;
125         });
126 
127     }
128 
129     DCDTask _getCompletionsTask;
130     override void getCompletions(DSourceEdit editor, TextPosition caretPosition, void delegate(dstring[] completions, string[] icons, CompletionTypes type) callback) {
131         cancelGetCompletions();
132         string[] importPaths = editor.importPaths(_frame.settings);
133 
134         string content = toUTF8(editor.text);
135         auto byteOffset = caretPositionToByteOffset(content, caretPosition);
136         _getCompletionsTask = _frame.dcdInterface.getCompletions(editor.window, importPaths, editor.filename, content, byteOffset, delegate(CompletionResultSet output) {
137             string[] icons;
138             dstring[] labels;
139             foreach(index, label; output.output) {
140                 string iconId;
141                 char ch = label.kind;
142                 switch(ch) {
143                     case 'c': // - class name
144                         iconId = "symbol-class";
145                         break;
146                     case 'i': // - interface name
147                         iconId = "symbol-interface";
148                         break;
149                     case 's': // - struct name
150                         iconId = "symbol-struct";
151                         break;
152                     case 'u': // - union name
153                         iconId = "symbol-union";
154                         break;
155                     case 'v': // - variable name
156                         iconId = "symbol-var";
157                         break;
158                     case 'm': // - member variable name
159                         iconId = "symbol-membervar";
160                         break;
161                     case 'k': // - keyword, built-in version, scope statement
162                         iconId = "symbol-keyword";
163                         break;
164                     case 'f': // - function or method
165                         iconId = "symbol-function";
166                         break;
167                     case 'g': // - enum name
168                         iconId = "symbol-enum";
169                         break;
170                     case 'e': // - enum member
171                         iconId = "symbol-enum";
172                         break;
173                     case 'P': // - package name
174                         iconId = "symbol-package";
175                         break;
176                     case 'M': // - module name
177                         iconId = "symbol-module";
178                         break;
179                     case 'a': // - array
180                         iconId = "symbol-array";
181                         break;
182                     case 'A': // - associative array
183                         iconId = "symbol-array";
184                         break;
185                     case 'l': // - alias name
186                         iconId = "symbol-alias";
187                         break;
188                     case 't': // - template name
189                         iconId = "symbol-template";
190                         break;
191                     case 'T': // - mixin template name
192                         iconId = "symbol-mixintemplate";
193                         break;
194                     default:
195                         iconId = "symbol-other";
196                         break;
197                 }
198                 icons ~= iconId;
199                 labels ~= label.name;
200             }
201             callback(labels, icons, output.type);
202             _getCompletionsTask = null;
203         });
204     }
205 
206 private:
207 
208 }
209 
210 /// convert caret position to byte offset in utf8 content
211 int caretPositionToByteOffset(string content, TextPosition caretPosition) {
212     auto line = 0;
213     auto pos = 0;
214     auto bytes = 0;
215     foreach(c; content) {
216         if(line == caretPosition.line) {
217             if(pos >= caretPosition.pos)
218                 break;
219             if ((c & 0xC0) != 0x80)
220                 pos++;
221         } else if (line > caretPosition.line) {
222             break;
223         }
224         bytes++;
225         if(c == '\n') {
226             line++;
227             pos = 0;
228         }
229     }
230     return bytes;
231 }
232 
233 /// convert byte offset in utf8 content to caret position
234 TextPosition byteOffsetToCaret(string content, int byteOffset) {
235     int bytes = 0;
236     int line = 0;
237     int pos = 0;
238     TextPosition textPos;
239     foreach(c; content) {
240         if(bytes >= byteOffset) {
241             //We all good.
242             textPos.line = line;
243             textPos.pos = pos;
244             return textPos;
245         }
246         bytes++;
247         if(c == '\n')
248         {
249             line++;
250             pos = 0;
251         }
252         else {
253             if ((c & 0xC0) != 0x80)
254                 pos++;
255         }
256     }
257     return textPos;
258 }