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