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 import dsymbol.string_interning : internString;
8 
9 import core.thread;
10 
11 import std.typecons;
12 import std.conv;
13 import std.string;
14 
15 import std.experimental.allocator;
16 import std.experimental.allocator.mallocator;
17 import std.experimental.allocator.gc_allocator;
18 
19 import server.autocomplete;
20 import common.messages;
21 import dsymbol.modulecache;
22 
23 //alias SharedASTAllocator = CAllocatorImpl!(Mallocator);
24 //alias SharedASTAllocator = CAllocatorImpl!(Mallocator);
25 //alias SharedASTAllocator = CSharedAllocatorImpl!(Mallocator);
26 alias SharedASTAllocator = ASTAllocator;
27 
28 enum DCDResult : int {
29     SUCCESS,
30     NO_RESULT,
31     FAIL,
32 }
33 
34 alias DocCommentsResultSet = Tuple!(DCDResult, "result", string[], "docComments");
35 alias FindDeclarationResultSet = Tuple!(DCDResult, "result", string, "fileName", ulong, "offset");
36 alias CompletionResultSet = Tuple!(DCDResult, "result", dstring[], "output", char[], "completionKinds");
37 
38 
39 class DCDTask {
40     protected bool _cancelled;
41     protected CustomEventTarget _guiExecutor;
42     protected string[] _importPaths;
43     protected string _filename;
44     protected string _content;
45     protected int _index;
46     protected AutocompleteRequest request;
47     this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index) {
48         _guiExecutor = guiExecutor;
49         _importPaths = importPaths;
50         _filename = filename;
51         _content = content;
52         _index = index;
53     }
54     @property bool cancelled() { return _cancelled; }
55     void cancel() {
56         synchronized(this) {
57             _cancelled = true;
58         }
59     }
60     void createRequest() {
61         request.sourceCode = cast(ubyte[])_content;
62         request.fileName = internString(_filename);
63         request.cursorPosition = _index;
64         request.importPaths = _importPaths;
65     }
66     void performRequest() {
67         // override
68     }
69     void postResults() {
70         // override
71     }
72     void execute() {
73         if (_cancelled)
74             return;
75         createRequest();
76         performRequest();
77         synchronized(this) {
78             if (_cancelled)
79                 return;
80             _guiExecutor.executeInUiThread(&postResults);
81         }
82     }
83 }
84 
85 string[] internStrings(in string[] src) {
86     if (!src)
87         return null;
88     string[] res;
89     foreach(s; src)
90         res ~= internString(s);
91     return res;
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(internStrings(importPaths));
101     }
102     protected ModuleCache * getModuleCache(in string[] importPaths) {
103         _moduleCache.addImportPaths(internStrings(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         Log.d("Starting DCD tasks thread");
165         while (!_queue.closed()) {
166             DCDTask task;
167             if (!_queue.get(task))
168                 break;
169             if (task && !task.cancelled) {
170                 import std.file : getcwd;
171                 Log.d("Execute DCD task; current dir=", getcwd);
172                 task.execute();
173                 Log.d("DCD task execution finished");
174             }
175         }
176         Log.d("Exiting DCD tasks thread");
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 DocCommentsTask : DCDTask {
196 
197         protected void delegate(DocCommentsResultSet output) _callback;
198         protected DocCommentsResultSet result;
199 
200         this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(DocCommentsResultSet output) callback) {
201             super(guiExecutor, importPaths, filename, content, index);
202             _callback = callback;
203         }
204 
205         override void performRequest() {
206             AutocompleteResponse response = getDoc(request, *getModuleCache(_importPaths));
207 
208             result.docComments = response.docComments.dup;
209             result.result = DCDResult.SUCCESS;
210 
211             debug(DCD) Log.d("DCD doc comments:\n", result.docComments);
212 
213             if (result.docComments is null) {
214                 result.result = DCDResult.NO_RESULT;
215             }
216         }
217         override void postResults() {
218             _callback(result);
219         }
220     }
221 
222     DCDTask getDocComments(CustomEventTarget guiExecutor, string[] importPaths, string filename, string content, int index, void delegate(DocCommentsResultSet output) callback) {
223         debug(DCD) Log.d("getDocComments: ", dumpContext(content, index));
224         DocCommentsTask task = new DocCommentsTask(guiExecutor, importPaths, filename, content, index, callback);
225         _queue.put(task);
226         return task;
227     }
228 
229     /// DCD go to definition task
230     class GoToDefinitionTask : DCDTask {
231 
232         protected void delegate(FindDeclarationResultSet output) _callback;
233         protected FindDeclarationResultSet result;
234 
235         this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(FindDeclarationResultSet output) callback) {
236             super(guiExecutor, importPaths, filename, content, index);
237             _callback = callback;
238         }
239 
240         override void performRequest() {
241             AutocompleteResponse response = findDeclaration(request, *getModuleCache(_importPaths));
242 
243             result.fileName = response.symbolFilePath.dup;
244             result.offset = response.symbolLocation;
245             result.result = DCDResult.SUCCESS;
246 
247             debug(DCD) Log.d("DCD fileName:\n", result.fileName);
248 
249             if (result.fileName is null) {
250                 result.result = DCDResult.NO_RESULT;
251             }
252         }
253         override void postResults() {
254             _callback(result);
255         }
256     }
257 
258     DCDTask goToDefinition(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(FindDeclarationResultSet res) callback) {
259 
260         debug(DCD) Log.d("DCD GoToDefinition task Context: ", dumpContext(content, index), " importPaths:", importPaths);
261         GoToDefinitionTask task = new GoToDefinitionTask(guiExecutor, importPaths, filename, content, index, callback);
262         _queue.put(task);
263         return task;
264     }
265 
266     /// DCD get code completions task
267     class GetCompletionsTask : DCDTask {
268 
269         protected void delegate(CompletionResultSet output) _callback;
270         protected CompletionResultSet result;
271 
272         this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(CompletionResultSet output) callback) {
273             super(guiExecutor, importPaths, filename, content, index);
274             _callback = callback;
275         }
276 
277         override void performRequest() {
278             AutocompleteResponse response = complete(request, *getModuleCache(_importPaths));
279             if(response.completions is null || response.completions.length == 0){
280                 result.result = DCDResult.NO_RESULT;
281                 return;
282             }
283 
284             result.result = DCDResult.SUCCESS;
285             result.output.length = response.completions.length;
286             result.completionKinds.length = response.completions.length;
287             int i=0;
288             foreach(s;response.completions) {
289                 char type = 0;
290                 if (i < response.completionKinds.length)
291                     type = response.completionKinds[i];
292                 result.completionKinds[i] = type;
293                 result.output[i++] = to!dstring(s);
294             }
295             debug(DCD) Log.d("DCD output:\n", response.completions);
296         }
297         override void postResults() {
298             _callback(result);
299         }
300     }
301 
302     DCDTask getCompletions(CustomEventTarget guiExecutor, string[] importPaths, string filename, string content, int index, void delegate(CompletionResultSet output) callback) {
303 
304         debug(DCD) Log.d("DCD Context: ", dumpContext(content, index));
305         GetCompletionsTask task = new GetCompletionsTask(guiExecutor, importPaths, filename, content, index, callback);
306         _queue.put(task);
307         return task;
308     }
309 
310 }
311 
312 
313 /// to test broken DCD after DUB invocation
314 /// run it after DCD ModuleCache is instantiated
315 void testDCDFailAfterThreadCreation() {
316     import core.thread;
317 
318     Log.d("testDCDFailAfterThreadCreation");
319     Thread thread = new Thread(delegate() {
320         Thread.sleep(dur!"msecs"(2000));
321     });
322     thread.start();
323     thread.join();
324     Log.d("testDCDFailAfterThreadCreation finished");
325 }
326