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