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