1 module dlangide.tools.d.dcdinterface;
2 
3 import dlangui.core.logger;
4 import dlangui.core.files;
5 import dlangui.platforms.common.platform;
6 import ddebug.common.queue;
7 
8 import core.thread;
9 
10 import std.typecons;
11 import std.conv;
12 import std..string;
13 
14 import std.experimental.allocator;
15 import std.experimental.allocator.mallocator;
16 import std.experimental.allocator.gc_allocator;
17 
18 import server.autocomplete;
19 import common.messages;
20 import dsymbol.modulecache;
21 
22 //alias SharedASTAllocator = CAllocatorImpl!(Mallocator);
23 //alias SharedASTAllocator = CAllocatorImpl!(Mallocator);
24 //alias SharedASTAllocator = CSharedAllocatorImpl!(Mallocator);
25 alias SharedASTAllocator = ASTAllocator;
26 
27 enum DCDResult : int {
28     SUCCESS,
29     NO_RESULT,
30     FAIL,
31 }
32 
33 struct CompletionSymbol {
34     dstring name;
35     char kind;
36 }
37 
38 alias DocCommentsResultSet = Tuple!(DCDResult, "result", string[], "docComments");
39 alias FindDeclarationResultSet = Tuple!(DCDResult, "result", string, "fileName", ulong, "offset");
40 alias CompletionResultSet = Tuple!(DCDResult, "result", CompletionSymbol[], "output");
41 
42 
43 class DCDTask {
44     protected bool _cancelled;
45     protected CustomEventTarget _guiExecutor;
46     protected string[] _importPaths;
47     protected string _filename;
48     protected string _content;
49     protected int _index;
50     protected AutocompleteRequest request;
51     this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index) {
52         _guiExecutor = guiExecutor;
53         _importPaths = importPaths;
54         _filename = filename;
55         _content = content;
56         _index = index;
57     }
58     @property bool cancelled() { return _cancelled; }
59     void cancel() {
60         synchronized(this) {
61             _cancelled = true;
62         }
63     }
64     void createRequest() {
65         request.sourceCode = cast(ubyte[])_content;
66         request.fileName = _filename;
67         request.cursorPosition = _index;
68         request.importPaths = _importPaths;
69     }
70     void performRequest() {
71         // override
72     }
73     void postResults() {
74         // override
75     }
76     void execute() {
77         if (_cancelled)
78             return;
79         createRequest();
80         if (_cancelled)
81             return;
82         performRequest();
83         synchronized(this) {
84             if (_cancelled)
85                 return;
86             if (_guiExecutor)
87                 _guiExecutor.executeInUiThread(&postResults);
88         }
89     }
90 }
91 
92 class ModuleCacheAccessor {
93     import dsymbol.modulecache;
94     //protected ASTAllocator _astAllocator;
95     protected ModuleCache _moduleCache;
96     this(in string[] importPaths) {
97         _moduleCache = ModuleCache(new SharedASTAllocator);
98         _moduleCache.addImportPaths(importPaths);
99     }
100     protected ModuleCache * getModuleCache(in string[] importPaths) {
101         _moduleCache.addImportPaths(importPaths);
102         return &_moduleCache;
103     }
104 }
105 
106 /// Async interface to DCD
107 class DCDInterface : Thread {
108 
109     import dsymbol.modulecache;
110     //protected ASTAllocator _astAllocator;
111     //protected ModuleCache * _moduleCache;
112     ModuleCacheAccessor _moduleCache;
113     protected BlockingQueue!DCDTask _queue;
114 
115     this() {
116         super(&threadFunc);
117         _queue = new BlockingQueue!DCDTask();
118         name = "DCDthread";
119         start();
120     }
121 
122     ~this() {
123         _queue.close();
124         join();
125         destroy(_queue);
126         _queue = null;
127         if (_moduleCache) {
128             destroyModuleCache();
129         }
130     }
131 
132     protected void destroyModuleCache() {
133         if (_moduleCache) {
134             Log.d("DCD: destroying module cache");
135             destroy(_moduleCache);
136             _moduleCache = null;
137             /*
138             if (_astAllocator) {
139                 _astAllocator.deallocateAll();
140                 destroy(_astAllocator);
141                 _astAllocator = null;
142             }
143             */
144         }
145     }
146 
147     protected ModuleCache * getModuleCache(in string[] importPaths) {
148         // TODO: clear cache if import paths removed or changed
149         // hold several module cache instances - make cache of caches
150         //destroyModuleCache();
151         //if (!_astAllocator)
152         //    _astAllocator = new ASTAllocator;
153         if (!_moduleCache) {
154             _moduleCache = new ModuleCacheAccessor(importPaths);
155         }
156         return _moduleCache.getModuleCache(importPaths);
157         //return _moduleCache;
158     }
159 
160     void threadFunc() {
161         _moduleCache = new ModuleCacheAccessor(null);
162         getModuleCache(null);
163         Log.d("Starting DCD tasks thread");
164         while (!_queue.closed()) {
165             DCDTask task;
166             if (!_queue.get(task))
167                 break;
168             if (task && !task.cancelled) {
169                 import std.file : getcwd;
170                 Log.d("Execute DCD task; current dir=", getcwd);
171                 task.execute();
172                 Log.d("DCD task execution finished");
173             }
174         }
175         Log.d("Exiting DCD tasks thread");
176         destroyModuleCache();
177     }
178 
179     import dsymbol.modulecache;
180 
181     protected string dumpContext(string content, int pos) {
182         if (pos >= 0 && pos <= content.length) {
183             int start = pos;
184             int end = pos;
185             for (int i = 0; start > 0 && content[start - 1] != '\n' && i < 10; i++)
186                 start--;
187             for (int i = 0; end < content.length - 1 && content[end] != '\n' && i < 10; i++)
188                 end++;
189             return content[start .. pos] ~ "|" ~ content[pos .. end];
190         }
191         return "";
192     }
193 
194     /// DCD doc comments task
195     class ModuleCacheWarmupTask : DCDTask {
196 
197         this(CustomEventTarget guiExecutor, string[] importPaths) {
198             super(guiExecutor, importPaths, null, null, 0);
199         }
200 
201         override void performRequest() {
202             debug(DCD) Log.d("DCD - warm up module cache with import paths ", _importPaths);
203             getModuleCache(_importPaths);
204             debug(DCD) Log.d("DCD - module cache warm up finished");
205         }
206         override void postResults() {
207         }
208     }
209 
210     DCDTask warmUp(string[] importPaths) {
211         debug(DCD) Log.d("DCD warmUp: ", importPaths);
212         ModuleCacheWarmupTask task = new ModuleCacheWarmupTask(null, importPaths);
213         _queue.put(task);
214         return task;
215     }
216 
217     /// DCD doc comments task
218     class DocCommentsTask : DCDTask {
219 
220         protected void delegate(DocCommentsResultSet output) _callback;
221         protected DocCommentsResultSet result;
222 
223         this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(DocCommentsResultSet output) callback) {
224             super(guiExecutor, importPaths, filename, content, index);
225             _callback = callback;
226         }
227 
228         override void performRequest() {
229             AutocompleteResponse response = getDoc(request, *getModuleCache(_importPaths));
230 
231             result.docComments = response.docComments.dup;
232             result.result = DCDResult.SUCCESS;
233 
234             debug(DCD) Log.d("DCD doc comments:\n", result.docComments);
235 
236             if (result.docComments is null) {
237                 result.result = DCDResult.NO_RESULT;
238             }
239         }
240         override void postResults() {
241             _callback(result);
242         }
243     }
244 
245     DCDTask getDocComments(CustomEventTarget guiExecutor, string[] importPaths, string filename, string content, int index, void delegate(DocCommentsResultSet output) callback) {
246         debug(DCD) Log.d("getDocComments: ", dumpContext(content, index));
247         DocCommentsTask task = new DocCommentsTask(guiExecutor, importPaths, filename, content, index, callback);
248         _queue.put(task);
249         return task;
250     }
251 
252     /// DCD go to definition task
253     class GoToDefinitionTask : DCDTask {
254 
255         protected void delegate(FindDeclarationResultSet output) _callback;
256         protected FindDeclarationResultSet result;
257 
258         this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(FindDeclarationResultSet output) callback) {
259             super(guiExecutor, importPaths, filename, content, index);
260             _callback = callback;
261         }
262 
263         override void performRequest() {
264             AutocompleteResponse response = findDeclaration(request, *getModuleCache(_importPaths));
265 
266             result.fileName = response.symbolFilePath.dup;
267             result.offset = response.symbolLocation;
268             result.result = DCDResult.SUCCESS;
269 
270             debug(DCD) Log.d("DCD fileName:\n", result.fileName);
271 
272             if (result.fileName is null) {
273                 result.result = DCDResult.NO_RESULT;
274             }
275         }
276         override void postResults() {
277             _callback(result);
278         }
279     }
280 
281     DCDTask goToDefinition(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(FindDeclarationResultSet res) callback) {
282 
283         debug(DCD) Log.d("DCD GoToDefinition task Context: ", dumpContext(content, index), " importPaths:", importPaths);
284         GoToDefinitionTask task = new GoToDefinitionTask(guiExecutor, importPaths, filename, content, index, callback);
285         _queue.put(task);
286         return task;
287     }
288 
289     /// DCD get code completions task
290     class GetCompletionsTask : DCDTask {
291 
292         protected void delegate(CompletionResultSet output) _callback;
293         protected CompletionResultSet result;
294 
295         this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(CompletionResultSet output) callback) {
296             super(guiExecutor, importPaths, filename, content, index);
297             _callback = callback;
298         }
299 
300         override void performRequest() {
301             AutocompleteResponse response = complete(request, *getModuleCache(_importPaths));
302             if(response.completions is null || response.completions.length == 0){
303                 result.result = DCDResult.NO_RESULT;
304                 return;
305             }
306 
307             result.result = DCDResult.SUCCESS;
308             result.output.length = response.completions.length;
309             int i=0;
310             foreach(s;response.completions) {
311                 char type = 0;
312                 if (i < response.completionKinds.length)
313                     type = response.completionKinds[i];
314                 result.output[i].kind = type;
315                 result.output[i].name = to!dstring(s);
316                 i++;
317             }
318             postProcessCompletions(result.output);
319             debug(DCD) Log.d("DCD response:\n", response, "\nCompletion result:\n", result.output);
320         }
321         override void postResults() {
322             _callback(result);
323         }
324     }
325 
326     DCDTask getCompletions(CustomEventTarget guiExecutor, string[] importPaths, string filename, string content, int index, void delegate(CompletionResultSet output) callback) {
327 
328         debug(DCD) Log.d("DCD Context: ", dumpContext(content, index));
329         GetCompletionsTask task = new GetCompletionsTask(guiExecutor, importPaths, filename, content, index, callback);
330         _queue.put(task);
331         return task;
332     }
333 
334 }
335 
336 int completionTypePriority(char t) {
337     switch(t) {
338         case 'c': // - class name
339             return 10;
340         case 'i': // - interface name
341             return 10;
342         case 's': // - struct name
343             return 10;
344         case 'u': // - union name
345             return 10;
346         case 'v': // - variable name
347             return 5;
348         case 'm': // - member variable name
349             return 3;
350         case 'k': // - keyword, built-in version, scope statement
351             return 20;
352         case 'f': // - function or method
353             return 2;
354         case 'g': // - enum name
355             return 9;
356         case 'e': // - enum member
357             return 8;
358         case 'P': // - package name
359             return 30;
360         case 'M': // - module name
361             return 20;
362         case 'a': // - array
363             return 15;
364         case 'A': // - associative array
365             return 15;
366         case 'l': // - alias name
367             return 15;
368         case 't': // - template name
369             return 14;
370         case 'T': // - mixin template name
371             return 14;
372         default:
373             return 50;
374     }
375 }
376 
377 int compareCompletionSymbol(ref CompletionSymbol v1, ref CompletionSymbol v2) {
378     import std.algorithm : cmp;
379     int p1 = v1.kind.completionTypePriority;
380     int p2 = v2.kind.completionTypePriority;
381     if (p1 < p2)
382         return -1;
383     if (p1 > p2)
384         return 1;
385     return v1.name.cmp(v2.name);
386 }
387 
388 bool lessCompletionSymbol(ref CompletionSymbol v1, ref CompletionSymbol v2) {
389     return compareCompletionSymbol(v1, v2) < 0;
390 }
391 
392 void postProcessCompletions(ref CompletionSymbol[] completions) {
393     import std.algorithm.sorting : sort;
394     completions.sort!(lessCompletionSymbol);
395     CompletionSymbol[] res;
396     bool hasKeywords = false;
397     bool hasNonKeywords = false;
398     bool[dstring] found;
399     foreach(s; completions) {
400         if (s.kind == 'k')
401             hasKeywords = true;
402         else
403             hasNonKeywords = true;
404     }
405     // remove duplicates; remove keywords if non-keyword items are found
406     foreach(s; completions) {
407         if (!(s.name in found)) {
408             found[s.name] = true;
409             if (s.kind != 'k' || !hasNonKeywords) {
410                 res ~= s;
411             }
412         }
413     }
414     completions = res;
415 }
416 
417 
418 /// to test broken DCD after DUB invocation
419 /// run it after DCD ModuleCache is instantiated
420 void testDCDFailAfterThreadCreation() {
421     import core.thread;
422 
423     Log.d("testDCDFailAfterThreadCreation");
424     Thread thread = new Thread(delegate() {
425         Thread.sleep(dur!"msecs"(2000));
426     });
427     thread.start();
428     thread.join();
429     Log.d("testDCDFailAfterThreadCreation finished");
430 }
431