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                     _frame.caretHistory.pushNewPosition();
121                     break;
122                 default:
123                     break;
124             }
125             _goToDefinitionTask = null;
126         });
127 
128     }
129 
130     DCDTask _getCompletionsTask;
131     override void getCompletions(DSourceEdit editor, TextPosition caretPosition, void delegate(dstring[] completions, string[] icons, CompletionTypes type) callback) {
132         cancelGetCompletions();
133         string[] importPaths = editor.importPaths(_frame.settings);
134 
135         string content = toUTF8(editor.text);
136         auto byteOffset = caretPositionToByteOffset(content, caretPosition);
137         _getCompletionsTask = _frame.dcdInterface.getCompletions(editor.window, importPaths, editor.filename, content, byteOffset, delegate(CompletionResultSet output) {
138             string[] icons;
139             dstring[] labels;
140             foreach(index, label; output.output) {
141                 string iconId;
142                 char ch = label.kind;
143                 switch(ch) {
144                     case 'c': // - class name
145                         iconId = "symbol-class";
146                         break;
147                     case 'i': // - interface name
148                         iconId = "symbol-interface";
149                         break;
150                     case 's': // - struct name
151                         iconId = "symbol-struct";
152                         break;
153                     case 'u': // - union name
154                         iconId = "symbol-union";
155                         break;
156                     case 'v': // - variable name
157                         iconId = "symbol-var";
158                         break;
159                     case 'm': // - member variable name
160                         iconId = "symbol-membervar";
161                         break;
162                     case 'k': // - keyword, built-in version, scope statement
163                         iconId = "symbol-keyword";
164                         break;
165                     case 'f': // - function or method
166                         iconId = "symbol-function";
167                         break;
168                     case 'g': // - enum name
169                         iconId = "symbol-enum";
170                         break;
171                     case 'e': // - enum member
172                         iconId = "symbol-enum";
173                         break;
174                     case 'P': // - package name
175                         iconId = "symbol-package";
176                         break;
177                     case 'M': // - module name
178                         iconId = "symbol-module";
179                         break;
180                     case 'a': // - array
181                         iconId = "symbol-array";
182                         break;
183                     case 'A': // - associative array
184                         iconId = "symbol-array";
185                         break;
186                     case 'l': // - alias name
187                         iconId = "symbol-alias";
188                         break;
189                     case 't': // - template name
190                         iconId = "symbol-template";
191                         break;
192                     case 'T': // - mixin template name
193                         iconId = "symbol-mixintemplate";
194                         break;
195                     default:
196                         iconId = "symbol-other";
197                         break;
198                 }
199                 icons ~= iconId;
200                 labels ~= label.name;
201             }
202             callback(labels, icons, output.type);
203             _getCompletionsTask = null;
204         });
205     }
206 
207 private:
208 
209 }
210 
211 /// convert caret position to byte offset in utf8 content
212 int caretPositionToByteOffset(string content, TextPosition caretPosition) {
213     auto line = 0;
214     auto pos = 0;
215     auto bytes = 0;
216     foreach(c; content) {
217         if(line == caretPosition.line) {
218             if(pos >= caretPosition.pos)
219                 break;
220             if ((c & 0xC0) != 0x80)
221                 pos++;
222         } else if (line > caretPosition.line) {
223             break;
224         }
225         bytes++;
226         if(c == '\n') {
227             line++;
228             pos = 0;
229         }
230     }
231     return bytes;
232 }
233 
234 /// convert byte offset in utf8 content to caret position
235 TextPosition byteOffsetToCaret(string content, int byteOffset) {
236     int bytes = 0;
237     int line = 0;
238     int pos = 0;
239     TextPosition textPos;
240     foreach(c; content) {
241         if(bytes >= byteOffset) {
242             //We all good.
243             textPos.line = line;
244             textPos.pos = pos;
245             return textPos;
246         }
247         bytes++;
248         if(c == '\n')
249         {
250             line++;
251             pos = 0;
252         }
253         else {
254             if ((c & 0xC0) != 0x80)
255                 pos++;
256         }
257     }
258     return textPos;
259 }