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