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